/*aprs decode*/
/*add by:nestor*/

#ifdef CONIC
#    include <conicconnection.h>
#    include <conicconnectionevent.h>
#endif

#ifndef LEGACY
#    include <hildon/hildon-program.h>
#    include <hildon/hildon-banner.h>
#    include <hildon/hildon-note.h>

#else
#    include <hildon-widgets/hildon-program.h>
#    include <hildon-widgets/hildon-banner.h>
#endif

#include <hildon/hildon-program.h>
#include <hildon/hildon-pannable-area.h>
#include <gtk/gtkmain.h>
#include <gtk/gtkbutton.h>
#include <hildon/hildon-note.h>
#include <hildon/hildon-color-button.h>
#include <hildon/hildon-file-chooser-dialog.h>
#include <hildon/hildon-number-editor.h>
#include <hildon/hildon-banner.h>
#include <hildon/hildon-pannable-area.h>
#include <hildon/hildon-picker-button.h>
#include <hildon/hildon-check-button.h>
#include <hildon/hildon-entry.h>

#include <libosso.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <stdlib.h>
#include <math.h>
#include <ctype.h>
#include <unistd.h>
#include <glib.h>
#include <glib/gstdio.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <time.h>
#include <pwd.h>

#include <termios.h>
#include <dbus/dbus.h>
#include <dbus/dbus-glib.h>

#include <pthread.h>
#include <libgnomevfs/gnome-vfs.h>
#include <libgnomevfs/gnome-vfs-inet-connection.h>

#include "hashtable.h"
#include "defines.h"
#include "data.h"
#include "main.h"
#include "types.h"
#include "poi.h"
#include "route.h"
#include "util.h"
#include <mappero-extras/dialog.h>


#include "controller-priv.h"



/*add by:nestor aprs inet tread*/
//static volatile GThread*	_aprs_inet_thread = NULL;
/*add end*/
/**APRS */
GnomeVFSInetConnection *iconn_aprs = NULL;
GnomeVFSSocket *socket_aprs = NULL;
GMutex *_aprs_tnc_mutex = NULL;
#define VERSIONFRM 	"APRS"


typedef enum
{
	APRS_PORT_INET,
	APRS_PORT_TTY,
	APRS_PORT_COUNT
} TAprsPort;

#define MAX_CALLSIGN 9
#define MAX_LINE_SIZE 			512
gboolean _aprs_show_new_station_alert = TRUE;
gint	_list_enabled=0;
gint     _aprs_std_pos_hist = 1;   // Not currently implemented, so fix as 1
gboolean _aprs_thread_inet_reading;
gboolean _aprs_thread_tnc_reading;
static guint _aprs_inet_beacon_timer = 0;
static guint _aprs_tty_beacon_timer = 0;

static volatile GThread*    _aprs_tty_thread = NULL;

/*
typedef enum
{
    UNITS_KM,
    UNITS_MI,
    UNITS_NM,
    UNITS_ENUM_COUNT
} UnitType;
UnitType _units = UNITS_KM;
*/

#define ST_OBJECT       0x01    // station is an object
#define ST_ITEM         0x02    // station is an item
#define ST_ACTIVE       0x04    // station is active (deleted objects are
                                // inactive)
#define ST_MOVING       0x08    // station is moving
#define ST_DIRECT       0x10    // heard direct (not via digis)
#define ST_VIATNC       0x20    // station heard via TNC
#define ST_3RD_PT       0x40    // third party traffic (not used yet)
#define ST_MSGCAP       0x80    // message capable (not used yet)
#define ST_STATUS       0x100   // got real status message
#define ST_INVIEW       0x200   // station is in current screen view
#define ST_MYSTATION    0x400   // station is owned by my call-SSID
#define ST_MYOBJITEM    0x800   // object/item owned by me
time_t last_sec,curr_sec;       // for comparing if seconds in time have changed
#define NORMAL_APRS     'N'
#define MOBILE_APRS     'M'
#define DF_APRS         'D'
#define DOWN_APRS       'Q'
#define NORMAL_GPS_RMC  'C'
#define NORMAL_GPS_GGA  'A'
#define NORMAL_GPS_GLL  'L'
#define MAX_ALTITUDE         10         //-32808.4 to 300000.0? feet
#define MAX_SPEED             9         /* ?? 3 in knots */
#define MAX_COURSE            7         /* ?? */
#define MAX_INFO_FIELD_SIZE 256
#define OBJECT_CHECK_RATE 20
#define TRAIL_MAX_SPEED      900    /* max. acceptible speed for drawing trails, in mph */
#define MAX_TIME             20
#define MAX_WXSTATION        50
int st_direct_timeout = 60 * 60;        // 60 minutes.
int redraw_on_new_data;         // Station redraw request
int  tracked_stations = 0;       // A count variable used in debug code only
int trail_segment_distance;     // Segment missing if greater distance
int trail_segment_time;         // Segment missing if above this time (mins)
int skip_dupe_checking;
#define MY_TRAIL_COLOR      0x16    /* trail color index reserved for my station */
#define MAX_TRAIL_COLORS 32
int my_trail_diff_color = 0;
int current_trail_color;        /* what color to draw station trails with */


int next_time_sn;               // time serial number for unique time index
#define MAX_POWERGAIN         7
#define MAX_MULTIPOINTS 35
#define MAX_SAT               4
int  altnet;
char altnet_call[MAX_CALLSIGN+1];

typedef struct _Point Point;
struct _Point {
    gint unitx;
    gint unity;
    time_t time;
    gint altitude;
};
#define MAX_COMMENT_LINES 		20
#define MAX_TACTICAL_CALL 		20
int station_count;              // number of stored stations
#define MAX_STATUS_LINES 		20
#define CHECKMALLOC(m)  if (!m) { fprintf(stderr, "***** Malloc Failed *****\n"); exit(0); }
#define WX_TYPE 'X'
float rain_minute_total = 0.0;      // Total for last hour, hundredths of an inch
float rain_00 = 0.0;                // hundredths of an inch
float rain_24 = 0.0;                // hundredths of an inch
float gust[60];                     // High wind gust for each min. of last hour
int gust_write_ptr = 0;
int gust_read_ptr = 0;
int gust_last_write = 0;
float rain_base[24];                // hundredths of an inch
int rain_check = 0;                 // Flag for re-checking rain_total each hour
float rain_minute[60];              // Total rain for each min. of last hour, hundredths of an inch
//#define EARTH_RADIUS (3443.91847)
//#define PI   (3.14159265358979323846)
gint	_OffsetDB_lines=0;  //add by:nestor
double _offset_lat=0;
double _offset_lon=0;
#define deg2rad(deg) ((deg) * (PI / 180.0))
#define rad2deg(rad) ((rad) * (180.0 / PI))
#define START_STR " }"
#define LBRACE '{'
#define RBRACE '}'
#define MAX_MESSAGE_LENGTH  100
#define TACTICAL_HASH_SIZE 		1024
enum AprsAreaObjectTypes {
    AREA_OPEN_CIRCLE     = 0x0,
    AREA_LINE_LEFT       = 0x1,
    AREA_OPEN_ELLIPSE    = 0x2,
    AREA_OPEN_TRIANGLE   = 0x3,
    AREA_OPEN_BOX        = 0x4,
    AREA_FILLED_CIRCLE   = 0x5,
    AREA_LINE_RIGHT      = 0x6,
    AREA_FILLED_ELLIPSE  = 0x7,
    AREA_FILLED_TRIANGLE = 0x8,
    AREA_FILLED_BOX      = 0x9,
    AREA_MAX             = 0x9,
    AREA_NONE            = 0xF
};

enum AprsAreaObjectColors {
    AREA_BLACK_HI  = 0x0,
    AREA_BLUE_HI   = 0x1,
    AREA_GREEN_HI  = 0x2,
    AREA_CYAN_HI   = 0x3,
    AREA_RED_HI    = 0x4,
    AREA_VIOLET_HI = 0x5,
    AREA_YELLOW_HI = 0x6,
    AREA_GRAY_HI   = 0x7,
    AREA_BLACK_LO  = 0x8,
    AREA_BLUE_LO   = 0x9,
    AREA_GREEN_LO  = 0xA,
    AREA_CYAN_LO   = 0xB,
    AREA_RED_LO    = 0xC,
    AREA_VIOLET_LO = 0xD,
    AREA_YELLOW_LO = 0xE,
    AREA_GRAY_LO   = 0xF
};


typedef struct _GpsData GpsData;
struct _GpsData {
    gint fix;
    gint fixquality;
    gdouble lat;
    gdouble lon;
    gfloat speed;    /* in knots */
    gfloat maxspeed;    /* in knots */
    gfloat heading;
    gfloat hdop;
    gfloat pdop;
    gfloat vdop;
    gint satinview;
    gint satinuse;
    gint satforfix[12];
};

GpsData _gps;
static struct hashtable *tactical_hash = NULL;



#define MAX_ALTITUDE         10         //-32808.4 to 300000.0? feet
typedef struct {
    unsigned type : 4;
    unsigned color : 4;
    unsigned sqrt_lat_off : 8;
    unsigned sqrt_lon_off : 8;
    unsigned corridor_width : 16;
} AprsAreaObject;

typedef struct {
    char aprs_type;
    char aprs_symbol;
    char special_overlay;
    AprsAreaObject area_object;
} APRS_Symbol;

typedef struct _AprsTrackRow{
    long    trail_long_pos;     // coordinate of trail point
    long    trail_lat_pos;      // coordinate of trail point
    time_t  sec;                // date/time of position
    long    speed;              // in 0.1 km/h   undefined: -1
    int     course;             // in degrees    undefined: -1
    long    altitude;           // in 0.1 m      undefined: -99999
    char    flag;               // several flags, see below
    struct  _AprsTrackRow *prev;    // pointer to previous record in list
    struct  _AprsTrackRow *next;    // pointer to next record in list
} AprsTrackRow;
typedef struct _AprsMultipointRow{
    long multipoints[MAX_MULTIPOINTS][2];
} AprsMultipointRow;


typedef struct _AprsCommentRow{
    char   *text_ptr;           // Ptr to the comment text
    time_t sec_heard;           // Latest timestamp for this comment/status
    struct _AprsCommentRow *next;   // Ptr to next record or NULL
} AprsCommentRow;

typedef struct {                //                      strlen
    time_t  wx_sec_time;
    int     wx_storm;           // Set to one if severe storm
    char    wx_time[MAX_TIME];
    char    wx_course[4];       // in °                     3
    char    wx_speed[4];        // in mph                   3
    time_t  wx_speed_sec_time;
    char    wx_gust[4];         // in mph                   3
    char    wx_hurricane_radius[4];  //nautical miles       3
    char    wx_trop_storm_radius[4]; //nautical miles       3
    char    wx_whole_gale_radius[4]; // nautical miles      3
    char    wx_temp[5];         // in °F                    3
    char    wx_rain[10];        // in hundredths inch/h     3
    char    wx_rain_total[10];  // in hundredths inch
    char    wx_snow[6];         // in inches/24h            3
    char    wx_prec_24[10];     // in hundredths inch/day   3
    char    wx_prec_00[10];     // in hundredths inch       3
    char    wx_hum[5];          // in %                     3
    char    wx_baro[10];        // in hPa                   6
    char    wx_fuel_temp[5];    // in °F                    3
    char    wx_fuel_moisture[5];// in %                     2
    char    wx_type;
    char    wx_station[MAX_WXSTATION];
} AprsWeatherRow;



typedef struct _AprsDataRow {

    struct _AprsDataRow *n_next;    // pointer to next element in name ordered list
    struct _AprsDataRow *n_prev;    // pointer to previous element in name ordered
                                // list
    struct _AprsDataRow *t_newer;   // pointer to next element in time ordered
                                // list (newer)
    struct _AprsDataRow *t_older;   // pointer to previous element in time ordered
                                // list (older)


    char call_sign[MAX_CALLSIGN+1]; // call sign or name index or object/item
                                    // name
    char *tactical_call_sign;   // Tactical callsign.  NULL if not assigned
    APRS_Symbol aprs_symbol;
    long coord_lon;             // Xastir coordinates 1/100 sec, 0 = 180°W
    long coord_lat;             // Xastir coordinates 1/100 sec, 0 =  90°N
 	double	offsetlat;			//add by:nestor
	double	offsetlon;			//add by:nestor
	gint poi_id;				//add by:nestor

    int  time_sn;               // serial number for making time index unique
    time_t sec_heard;           // time last heard, used also for time index
    time_t heard_via_tnc_last_time;
    time_t direct_heard;        // KC2ELS - time last heard direct

// Change into time_t structs?  It'd save us a bunch of space.
    char packet_time[MAX_TIME];
    char pos_time[MAX_TIME];

    short flag;                 // several flags, see below
    char pos_amb;               // Position ambiguity, 0 = none,
                                // 1 = 0.1 minute...

    unsigned int error_ellipse_radius; // Degrades precision for this
                                // station, from 0 to 65535 cm or
                                // 655.35 meters.  Assigned when we
                                // decode each type of packet.
                                // Default is 6.0 meters (600 cm)
                                // unless we know the GPS position
                                // is augmented, or is degraded by
                                // less precision in the packet.

    unsigned int lat_precision;	// In 100ths of a second latitude
    unsigned int lon_precision;	// In 100ths of a second longitude

    int trail_color;            // trail color (when assigned)
    char record_type;
    //char data_via;              // L local, T TNC, I internet, F file

// Change to char's to save space?
    int  heard_via_tnc_port;    // Current this will always be 0, but keep for future
    TAprsPort  last_port_heard;       // Current this will always be 0, but keep for future
    unsigned int  num_packets;
    char *node_path_ptr;        // Pointer to path string
    char altitude[MAX_ALTITUDE]; // in meters (feet gives better resolution ??)
    char speed[MAX_SPEED+1];    // in knots (same as nautical miles/hour)
    char course[MAX_COURSE+1];
    char bearing[MAX_COURSE+1];
    char NRQ[MAX_COURSE+1];
    char power_gain[MAX_POWERGAIN+1];   // Holds the phgd values
    char signal_gain[MAX_POWERGAIN+1];  // Holds the shgd values (for DF'ing)

    AprsWeatherRow *weather_data;   // Pointer to weather data or NULL

    AprsCommentRow *status_data;    // Ptr to status records or NULL
    AprsCommentRow *comment_data;   // Ptr to comment records or NULL

    // Below two pointers are NULL if only one position has been received
    AprsTrackRow *oldest_trackpoint; // Pointer to oldest track point in
                                 // doubly-linked list
    AprsTrackRow *newest_trackpoint; // Pointer to newest track point in
                                 // doubly-linked list

    // When the station is an object, it can include coordinates
    // of related points. Currently these are being used to draw
    // outlines of NWS severe weather watches and warnings, and
    // storm regions. The coordinates are stored here in Xastir
    // coordinate form. Element [x][0] is the latitude, and
    // element [x][1] is the longitude.  --KG4NBB
    //
    // Is there anything preventing a multipoint string from being
    // in other types of packets, in the comment field?  --WE7U
    //
    int num_multipoints;
    char type;      // from '0' to '9'
    char style;     // from 'a' to 'z'
    AprsMultipointRow *multipoint_data;


///////////////////////////////////////////////////////////////////////
// Optional stuff for Objects/Items only (I think, needs to be
// checked).  These could be moved into an ObjectRow structure, with
// only a NULL pointer here if not an object/item.
///////////////////////////////////////////////////////////////////////

    char origin[MAX_CALLSIGN+1]; // call sign originating an object
    short object_retransmit;     // Number of times to retransmit object.
                                 // -1 = forever
                                 // Used currently to stop sending killed
                                 // objects.
    time_t last_transmit_time;   // Time we last transmitted an object/item.
                                 // Used to implement decaying transmit time
                                 // algorithm
    short transmit_time_increment; // Seconds to add to transmit next time
                                   // around.  Used to implement decaying
                                   // transmit time algorithm
//    time_t last_modified_time;   // Seconds since the object/item
                                 // was last modified.  We'll
                                 // eventually use this for
                                 // dead-reckoning.
    char signpost[5+1];          // Holds signpost data
    int  df_color;
    char sats_visible[MAX_SAT];
    char probability_min[10+1];  // Holds prob_min (miles)
    char probability_max[10+1];  // Holds prob_max (miles)

} AprsDataRow;


AprsDataRow *station_shortcuts[16384];
AprsDataRow *n_first;               // pointer to first element in name sorted station list
AprsDataRow *t_oldest;              // pointer to first element in time sorted station list (oldest)
AprsDataRow *t_newest;              // pointer to last  element in time sorted station list (newest)
AprsDataRow *n_last;                // pointer to last  element in name sorted station list

time_t posit_next_time;
time_t sec_now(void) {
    time_t timenw;
    time_t ret;

    ret = time(&timenw);
    return(ret);
}

#define TR_LOCAL        0x01    // heard direct (not via digis)
#define TR_NEWTRK       0x02    // start new track
char echo_digis[6][MAX_CALLSIGN+1];
int stations_heard = 0;
#define TRAIL_ECHO_TIME       30    /* check for delayed echos during last 30 minutes */
typedef enum
{
	TNC_CONNECTION_BT,
	TNC_CONNECTION_FILE
} TTncConnection;
void set_aprs_tty_conn_state(ConnState new_conn_state);





enum APRS_Types {
    APRS_NULL,
    APRS_MSGCAP,
    APRS_FIXED,
    APRS_DOWN,      // Not used anymore
    APRS_MOBILE,
    APRS_DF,
    APRS_OBJECT,
    APRS_ITEM,
    APRS_STATUS,
    APRS_WX1,
    APRS_WX2,
    APRS_WX3,
    APRS_WX4,
    APRS_WX5,
    APRS_WX6,
    QM_WX,
    PEET_COMPLETE,
    RSWX200,
    GPS_RMC,
    GPS_GGA,
    GPS_GLL,
    STATION_CALL_DATA,
    OTHER_DATA,
    APRS_MICE,
    APRS_GRID,
    DALLAS_ONE_WIRE,
    DAVISMETEO
};


typedef struct _OffsetDB TOffsetDB;	//add by:nestor 20110117 纠偏结构

struct _OffsetDB
{
	double lat1;
	double lon1;
	double lat2;
	double lon2;
};

TOffsetDB * _offset_china = NULL;



// KISS Protocol Special Characters & Commands:
#define KISS_FEND           0xc0  // Frame End
#define KISS_FESC           0xdb  // Frame Escape
#define KISS_TFEND          0xdc  // Transposed Frame End
#define KISS_TFESC          0xdd  // Transposed Frame Escape
#define KISS_DATA           0x00
#define KISS_TXDELAY        0x01
#define KISS_PERSISTENCE    0x02
#define KISS_SLOTTIME       0x03
#define KISS_TXTAIL         0x04
#define KISS_FULLDUPLEX     0x05
#define KISS_SETHARDWARE    0x06
#define KISS_RETURN         0xff


enum Device_Types {
    DEVICE_NONE,
    DEVICE_SERIAL_TNC,
    DEVICE_NET_STREAM,
    DEVICE_AX25_TNC,
    DEVICE_SERIAL_KISS_TNC,     // KISS TNC on serial port (not ax.25 kernel device)
    DEVICE_SERIAL_MKISS_TNC     // Multi-port KISS TNC, like the Kantronics KAM
};

enum Device_Active {
    DEVICE_NOT_IN_USE,
    DEVICE_IN_USE
};

enum Device_Status {
    DEVICE_DOWN,
    DEVICE_UP,
    DEVICE_ERROR
};
#define MAX_DEVICE_NAME 128
#define MAX_DEVICE_HOSTNM 40
#define MAX_DEVICE_HOSTPW 40
#define MAX_DEVICE_BUFFER_UNTIL_BINARY_SWITCH 700

#define DISABLE_SETUID_PRIVILEGE do { \
seteuid(getuid()); \
setegid(getgid()); \
} while(0)
#define ENABLE_SETUID_PRIVILEGE do { \
seteuid(euid); \
setegid(egid); \
} while(0)

typedef struct {
    int    device_type;                           /* device type                             */
    int    active;                                /* channel in use                          */
    int    status;                                /* current status (up or down)             */
    char   device_name[MAX_DEVICE_NAME+1];        /* device name                             */
    char   device_host_name[MAX_DEVICE_HOSTNM+1]; /* device host name for network            */
    unsigned long int address;                    /* socket address for network              */
    int    thread_status;                         /* thread status for connect thread        */
    int    connect_status;                        /* connect status for connect thread       */
    int    decode_errors;                         /* decode error count, used for data type  */
    int    data_type;                             /* 0=normal 1=wx_binary                    */
    int    socket_port;                           /* socket port# for network                */
    char   device_host_pswd[MAX_DEVICE_HOSTPW+1]; /* host password                           */
    int    channel;                               /* for serial and net ports                */
    int    channel2;                              /* for AX25 ports                          */
    char   ui_call[30];                           /* current call for this port              */
    struct termios t,t_old;                       /* terminal struct for serial port         */
    int    dtr;                                   /* dtr signal for HSP cable (status)       */
    int    sp;                                    /* serial port speed                       */
    int    style;                                 /* serial port style                       */
    int    scan;                                  /* data read available                     */
    int    errors;                                /* errors for this port                    */
    int    reconnect;                             /* reconnect on net failure                */
    int    reconnects;                            /* total number of reconnects by this port */
    unsigned long   bytes_input;                  /* total bytes read by this port           */
    unsigned long   bytes_output;                 /* total bytes written by this port        */
    unsigned long   bytes_input_last;             /* total bytes read last check             */
    unsigned long   bytes_output_last;            /* total bytes read last check             */
    int    port_activity;                         /* 0 if no activity between checks         */
    pthread_t read_thread;                        /* read thread                             */
    int    read_in_pos;                           /* current read buffer input pos           */
    int    read_out_pos;                          /* current read buffer output pos          */
    char   device_read_buffer[MAX_DEVICE_BUFFER]; /* read buffer for this port               */
    pthread_mutex_t read_lock;                       /* Lock for reading the port data          */
    pthread_t write_thread;                       /* write thread                            */
    int    write_in_pos;                          /* current write buffer input pos          */
    int    write_out_pos;                         /* current write buffer output pos         */
    pthread_mutex_t write_lock;                      /* Lock for writing the port data          */
    char   device_write_buffer[MAX_DEVICE_BUFFER];/* write buffer for this port              */
} iface;
uid_t euid;
gid_t egid;

iface port_data;     // shared port data
typedef struct _TWriteBuffer TWriteBuffer;
struct _TWriteBuffer
{
    int    write_in_pos;                          /* current write buffer input pos          */
    int    write_out_pos;                         /* current write buffer output pos         */
    GMutex* write_lock;                      /* Lock for writing the port data          */
    char   device_write_buffer[MAX_DEVICE_BUFFER];/* write buffer for this port              */
    int    errors;
};


TWriteBuffer _write_buffer[APRS_PORT_COUNT];
static gboolean kiss_parse_packet(gchar *data_string, gint data_length);
void close_tnc_port();
int serial_init ();
gboolean aprs_tty_connect();
void aprs_tty_disconnect();

gboolean is_aprs_inet_connected();
gboolean is_aprs_tnc_connected();
void aprs_inet_init();
void aprs_tnc_init();
void aprs_inet_destroy();
void aprs_tnc_destroy();

ConnState _aprs_tty_state = RCVR_OFF;
void output_igate_aprs_data(TAprsPort port,gchar* from_call, gchar* path, gchar* packet, gint packet_length);
















void auto_offset_init();
void auto_offset_set();
void cal_offset_correct(double fromlat,double fromlon,double *offsetlat,double *offsetlon);


/*void plot_aprs_station(AprsDataRow *p_station, gboolean single );*/

int is_aprs_chr(char ch) {

    if (g_ascii_isdigit(ch) || ch==' ' || ch=='.' || ch=='-')
    return(1);
    else
    return(0);
}

void substr(char *dest, char *src, int size)
{
    snprintf(dest, size+1, "%s", src);
}
void statusline(char *status_text,int update) {

// Maybe useful
/*
    XmTextFieldSetString (text, status_text);
    last_statusline = sec_now();    // Used for auto-ID timeout
*/
}
int position_defined(long lat, long lon, int strict) {

    if (lat == 0l && lon == 0l)
        return(0);              // undefined location
#ifndef ACCEPT_0N_0E
    if (strict)
#endif  // ACCEPT_0N_0E
        if (lat == 90*60*60*100l && lon == 180*60*60*100l)      // 0N/0E
            return(0);          // undefined location
    return(1);
}



/***********************************************************/
/* returns the hour (00..23), localtime                    */
/***********************************************************/
int get_hours(void) {
    struct tm *time_now;
    time_t secs_now;
    char shour[5];

    secs_now=sec_now();
    time_now = localtime(&secs_now);
    (void)strftime(shour,4,"%H",time_now);
    return(atoi(shour));
}
char *get_time(char *time_here) {
    struct tm *time_now;
    time_t timenw;

    (void)time(&timenw);
    time_now = localtime(&timenw);
    (void)strftime(time_here,MAX_TIME,"%m%d%Y%H%M%S",time_now);
    return(time_here);
}
char *remove_leading_spaces(char *data) {
    int i,j;
    int count;

    if (data == NULL)
        return NULL;

    if (strlen(data) == 0)
        return NULL;

    count = 0;
    // Count the leading space characters
    for (i = 0; i < (int)strlen(data); i++) {
        if (data[i] == ' ') {
            count++;
        }
        else {  // Found a non-space
            break;
        }
    }

    // Check whether entire string was spaces
    if (count == (int)strlen(data)) {
        // Empty the string
        data[0] = '\0';
    }
    else if (count > 0) {  // Found some spaces
        i = 0;
        for( j = count; j < (int)strlen(data); j++ ) {
            data[i++] = data[j];    // Move string left
        }
        data[i] = '\0'; // Terminate the new string
    }

    return(data);
}

unsigned int tactical_hash_from_key(void *key) {
    unsigned char *jj = key;
    unsigned int tac_hash = 1;

    while (*jj != '\0') {
       tac_hash = tac_hash * (unsigned int)*jj++;
    }

    tac_hash = tac_hash % TACTICAL_HASH_SIZE;

    return (tac_hash);
}


/***********************************************************/
/* returns the minute (00..59), localtime                  */
/***********************************************************/
int get_minutes(void) {
    struct tm *time_now;
    time_t secs_now;
    char sminute[5];

    secs_now=sec_now();
    time_now = localtime(&secs_now);
    (void)strftime(sminute,4,"%M",time_now);
    return(atoi(sminute));
}

int is_num_chr(char ch) {
    return((int)isdigit(ch));
}
int is_xnum_or_dash(char *data, int max){
    int i;
    int ok;

    ok=1;
    for(i=0; i<max;i++)
        if(!(isxdigit((int)data[i]) || data[i]=='-')) {
            ok=0;
            break;
        }

    return(ok);
}
double convert_lat_l2d(long lat)
{
    double dResult = 0.00;

    int nspn;
    float deg, min;
    int ideg;
    long temp;

    deg = (float)(lat - 32400000l) / 360000.0;

    // Switch to integer arithmetic to avoid floating-point
    // rounding errors.
    temp = (long)(deg * 100000);

    nspn = -1;
    if (temp <= 0) {
        nspn = 1;
        temp = labs(temp);
    }

    ideg = (int)temp / 100000;
    min = (temp % 100000) * 60.0 / 100000.0;

    dResult = nspn*(ideg+min/60.0);

    return dResult;
}
///////////////////
double convert_lon_l2d(long lon)
{
    double dResult = 0.00;

    int ewpn;
    float deg, min; //, sec;
    int ideg; //, imin;
    long temp;

    deg = (float)(lon - 64800000l) / 360000.0;

    // Switch to integer arithmetic to avoid floating-point rounding
    // errors.
    temp = (long)(deg * 100000);


    ewpn = 1;
    if (temp <= 0)
    {
        ewpn = -1;
        temp = labs(temp);
    }

    ideg = (int)temp / 100000;
    min = (temp % 100000) * 60.0 / 100000.0;


    dResult = ewpn*(ideg+min/60.0);

    return dResult;
}



int is_my_station(AprsDataRow *p_station) {
    // if station is owned by me (including SSID)
    return(p_station->flag & ST_MYSTATION);
}

int is_my_object_item(AprsDataRow *p_station) {
    // If object/item is owned by me (including SSID)
    return(p_station->flag & ST_MYOBJITEM);
}
void extract_area(AprsDataRow *p_station, char *data) {
    int i, val, len;
    unsigned int uval;
    AprsAreaObject temp_area;

    /* NOTE: If we are here, the symbol was the area symbol.  But if this
       is a slightly corrupted packet, we shouldn't blow away the area info
       for this station, since it could be from a previously received good
       packet.  So we will work on temp_area and only copy to p_station at
       the end, returning on any error as we parse. N7TAP */

    //fprintf(stderr,"Area Data: %s\n", data);

    len = (int)strlen(data);
    val = data[0] - '0';
    if (val >= 0 && val <= AREA_MAX) {
        temp_area.type = val;
        val = data[4] - '0';
        if (data[3] == '/') {
            if (val >=0 && val <= 9) {
                temp_area.color = val;
            }
            else {
                return;
            }
        }
        else if (data[3] == '1') {
            if (val >=0 && val <= 5) {
                temp_area.color = 10 + val;
            }
            else {
                return;
            }
        }

        val = 0;
        if (isdigit((int)data[1]) && isdigit((int)data[2])) {
            val = (10 * (data[1] - '0')) + (data[2] - '0');
        }
        else {
            return;
        }
        temp_area.sqrt_lat_off = val;

        val = 0;
        if (isdigit((int)data[5]) && isdigit((int)data[6])) {
            val = (10 * (data[5] - '0')) + (data[6] - '0');
        }
        else {
            return;
        }
        temp_area.sqrt_lon_off = val;

        for (i = 0; i <= len-7; i++) // delete area object from data extension field
            data[i] = data[i+7];
        len -= 7;

        if (temp_area.type == AREA_LINE_RIGHT || temp_area.type == AREA_LINE_LEFT) {
            if (data[0] == '{') {
                if (sscanf(data, "{%u}", &uval) == 1) {
                    temp_area.corridor_width = uval & 0xffff;
                    for (i = 0; i <= len; i++)
                        if (data[i] == '}')
                            break;
                    uval = i+1;
                    for (i = 0; i <= (int)(len-uval); i++)
                        data[i] = data[i+uval]; // delete corridor width
                }
                else {
                    temp_area.corridor_width = 0;
                    return;
                }
            }
            else {
                temp_area.corridor_width = 0;
            }
        }
        else {
            temp_area.corridor_width = 0;
        }
    }
    else {
        return;
    }

    memcpy(&(p_station->aprs_symbol.area_object), &temp_area, sizeof(AprsAreaObject));

}


int extract_probability_max(char *info, char *prob_max, int prob_max_size) {
    int len,done;
    char *c;
    char *d;



    len = (int)strlen(info);
    if (len < 6) {          // Too short
        prob_max[0] = '\0';
        return(0);
    }

    c = strstr(info,"Pmax");
    if (c == NULL) {        // Pmax not found
        prob_max[0] = '\0';
        return(0);
    }

    c = c+4;    // Skip the Pmax part
    // Find the ending comma
    d = c;
    done = 0;
    while (!done) {
        if (*d == ',') {    // We're done
            done++;
        }
        else {
            d++;
        }

        // Check for string too long
        if ( ((d-c) > 10) && !done) {    // Something is wrong, we should be done by now
            prob_max[0] = '\0';
            return(0);
        }
    }

    // Copy the substring across
    snprintf(prob_max,
        prob_max_size,
        "%s",
        c);
    prob_max[d-c] = '\0';
    prob_max[10] = '\0';    // Just to make sure

    // Delete data from data extension field
    d++;    // Skip the comma
    done = 0;
    while (!done) {
        *(c-4) = *d;
        if (*d == '\0')
            done++;
        c++;
        d++;
    }

    return(1);
}




/*
 *  Extract probability_min data from APRS info field: "Pmin1.23,"
 *  Please note the ending comma.  We use it to delimit the field.
 */
int extract_probability_min(char *info, char *prob_min, int prob_min_size) {
    int len,done;
    char *c;
    char *d;


    len = (int)strlen(info);
    if (len < 6) {          // Too short
        prob_min[0] = '\0';
        return(0);
    }

    c = strstr(info,"Pmin");
    if (c == NULL) {        // Pmin not found
        prob_min[0] = '\0';
        return(0);
    }

    c = c+4;    // Skip the Pmin part
    // Find the ending comma
    d = c;
    done = 0;
    while (!done) {
        if (*d == ',') {    // We're done
            done++;
        }
        else {
            d++;
        }

        // Check for string too long
        if ( ((d-c) > 10) && !done) {    // Something is wrong, we should be done by now
            prob_min[0] = '\0';
            return(0);
        }
    }

    // Copy the substring across
    snprintf(prob_min,
        prob_min_size,
        "%s",
        c);
    prob_min[d-c] = '\0';
    prob_min[10] = '\0';    // Just to make sure

    // Delete data from data extension field
    d++;    // Skip the comma
    done = 0;
    while (!done) {
        *(c-4) = *d;
        if (*d == '\0')
            done++;
        c++;
        d++;
    }

    return(1);
}


int extract_signpost(char *info, char *signpost) {
    int i,found,len,done;

//0123456
//{1}
//{12}
//{121}

    found=0;
    len = (int)strlen(info);
    if ( (len > 2)
            && (info[0] == '{')
            && ( (info[2] == '}' ) || (info[3] == '}' ) || (info[4] == '}' ) ) ) {

        i = 1;
        done = 0;
        while (!done) {                 // Snag up to three digits
            if (info[i] == '}') {       // We're done
                found = i;              // found = position of '}' character
                done++;
            }
            else {
                signpost[i-1] = info[i];
            }

            i++;

            if ( (i > 4) && !done) {    // Something is wrong, we should be done by now
                done++;
                signpost[0] = '\0';
                return(0);
            }
        }
        substr(signpost,info+1,found-1);
        found++;
        for (i=0;i<=len-found;i++) {    // delete omnidf from data extension field
            info[i] = info[i+found];
        }
        return(1);
    }
    else {
        signpost[0] = '\0';
        return(0);
    }
}


int is_altnet(AprsDataRow *p_station) {
    char temp_altnet_call[20+1];
    char temp2[20+1];
    char *net_ptr;
    int  altnet_match;
    int  result;


    // Snag a possible altnet call out of the record for later use
    if (p_station->node_path_ptr != NULL)
        substr(temp_altnet_call, p_station->node_path_ptr, MAX_CALLSIGN);
    else
        temp_altnet_call[0] = '\0';

    // Save for later
    snprintf(temp2,
        sizeof(temp2),
        "%s",
        temp_altnet_call);

    if ((net_ptr = strchr(temp_altnet_call, ',')))
        *net_ptr = '\0';    // Chop the string at the first ',' character

    for (altnet_match = (int)strlen(altnet_call); altnet && altnet_call[altnet_match-1] == '*'; altnet_match--);

    result = (!strncmp(temp_altnet_call, altnet_call, (size_t)altnet_match)
                 || !strcmp(temp_altnet_call, "local")
                 || !strncmp(temp_altnet_call, "SPC", 3)
                 || !strcmp(temp_altnet_call, "SPECL")
                 || ( is_my_station(p_station) ) ) ;  // It's my callsign/SSID

    return(result);
}


int is_num_or_sp(char ch)
{
    return((int)((ch >= '0' && ch <= '9') || ch == ' '));
}
int is_weather_data(char *data, int len) {
    int ok = 1;
    int i;
    int count = 0;

    for (i=0;ok && i<len;i++)
        if (!is_aprs_chr(data[i]))
            ok = 0;

    // Count filler characters.  Must equal zero or field width to
    // be a weather field.  There doesn't appear to be a case where
    // a single period is allowed in any weather-related fields.
    //
    for (i=0;ok && i<len;i++) {
        if (data[i] == ' ' || data[i] == '.') {
            count++;
        }
    }
    if (count != 0 && count != len) {
        ok = 0;
    }

    return(ok);
}
void insert_time(AprsDataRow *p_new, AprsDataRow *p_time) {


    // Set up pointer to next record (or NULL), sorted by time
    p_new->t_newer = p_time;

    if (p_time == NULL) {               // add to end of list (becomes newest station)

        p_new->t_older = t_newest;         // connect to previous end of list

        if (t_newest == NULL)             // if list empty, create list
            t_oldest = p_new;            // it's now our only station on the list
        else
            t_newest->t_newer = p_new;     // list not empty, link original last record to our new one

        t_newest = p_new;                 // end of list (newest record pointer) points to our new record
    }

    else {                            // Else we're inserting into the middle of the list somewhere

        p_new->t_older = p_time->t_older;

        if (p_time->t_older == NULL)     // add to end of list (new record becomes oldest station)
            t_oldest = p_new;
        else
            p_time->t_older->t_newer = p_new; // else

        p_time->t_older = p_new;
    }

    // TODO - this may need implementing?
    /*
    if(_aprs_max_stations > 0 && stations_heard > _aprs_max_stations)
    {
    	// Delete the oldest
    	if(t_oldest != NULL)
    	{
  //  		fprintf(stderr, "DEBUG: oldest station deleted\n");

    		delete_station_memory(t_oldest);
    	}
    	else
    	{
    		stations_heard++;
    	}
    }
    else
    {
//    	fprintf(stderr, "DEBUG: Stations in memory: %d\n", stations_heard);

    	stations_heard++; // Should really be done in the new station method
    }
    */
}

void remove_time(AprsDataRow *p_rem) {      // todo: return pointer to next element

    if (p_rem->t_older == NULL) { // Appears to be first element in list

        if (t_oldest == p_rem) {  // Yes, head of list (oldest)

            // Make oldest list head point to 2nd element in list (or NULL)
            // so that we can delete the current record.
            t_oldest = p_rem->t_newer;
        }
        else {  // No, not first (oldest) element in list.  Problem!
                // The list pointers are inconsistent for some
                // reason.  The chain has been broken and we have
                // dangling pointers.

            fprintf(stderr,
                "remove_time(): ERROR: p->t_older == NULL but p != t_oldest\n");

abort();    // Cause a core dump at this point
// Perhaps we could do some repair to the list pointers here?  Start
// at the other end of the chain and navigate back to this end, then
// fix up t_oldest to point to it?  This is at the risk of a memory
// leak, but at least Xastir might continue to run.

        }
    }
    else {  // Not the first (oldest) element in the list.  Fix up
            // pointers to skip the current record.
        p_rem->t_older->t_newer = p_rem->t_newer;
    }


    if (p_rem->t_newer == NULL) { // Appears to be last (newest) element in list

        if (t_newest == p_rem) {   // Yes, head of list (newest)

            // Make newest list head point to previous element in
            // list (or NULL) so that we can delete the current
            // record.
            t_newest = p_rem->t_older;
        }
        else {  // No, not newest element in list.  Problem!  The
                // list pointers are inconsistent for some reason.
                // The chain has been broken and we have dangling
                // pointers.

            fprintf(stderr,
                "remove_time(): ERROR: p->t_newer == NULL but p != t_newest\n");

abort();    // Cause a core dump at this point
// Perhaps we could do some repair to the list pointers here?  Start
// at the other end of the chain and navigate back to this end, then
// fix up t_newest to point to it?  This is at the risk of a memory
// leak, but at least Xastir might continue to run.

        }
    }
    else {  // Not the newest element in the list.  Fix up pointers
            // to skip the current record.
        p_rem->t_newer->t_older = p_rem->t_older;
    }

    stations_heard--;
}
void move_station_time(AprsDataRow *p_curr, AprsDataRow *p_time) {

    if (p_curr != NULL) {               // need a valid record
        remove_time(p_curr);
        insert_time(p_curr,p_time);
    }
}
int extract_powergain_range(char *info, char *phgd) {
    int i,found,len;
    char *info2;


//fprintf(stderr,"Info:%s\n",info);

    // Check whether two strings of interest are present and snag a
    // pointer to them.
    info2 = strstr(info,"RNG");
    if (!info2)
        info2 = strstr(info,"PHG");
    if (!info2) {
        phgd[0] = '\0';
        return(0);
    }

    found=0;
    len = (int)strlen(info2);

    if (len >= 9 && strncmp(info2,"PHG",3)==0
            && info2[7]=='/'
            && info2[8]!='A'  // trailing '/' not defined in Reference...
            && g_ascii_isdigit(info2[3])
            && g_ascii_isdigit(info2[4])
            && g_ascii_isdigit(info2[5])
            && g_ascii_isdigit(info2[6])) {
        substr(phgd,info2,7);
        found = 1;
        for (i=0;i<=len-8;i++)        // delete powergain from data extension field
            info2[i] = info2[i+8];
    }
    else {
        if (len >= 7 && strncmp(info2,"PHG",3)==0
                && g_ascii_isdigit(info2[3])
                && g_ascii_isdigit(info2[4])
                && g_ascii_isdigit(info2[5])
                && g_ascii_isdigit(info2[6])) {
            substr(phgd,info2,7);
            found = 1;
            for (i=0;i<=len-7;i++)        // delete powergain from data extension field
                info2[i] = info2[i+7];
        }
        else if (len >= 7 && strncmp(info2,"RNG",3)==0
                && g_ascii_isdigit(info2[3])
                && g_ascii_isdigit(info2[4])
                && g_ascii_isdigit(info2[5])
                && g_ascii_isdigit(info2[6])) {
            substr(phgd,info2,7);
            found = 1;
            for (i=0;i<=len-7;i++)        // delete powergain from data extension field
                info2[i] = info2[i+7];
        }
        else {
            phgd[0] = '\0';
        }
    }
    return(found);
}


int search_station_name(AprsDataRow **p_name, char *call, int exact) {
    int kk;
    int hash_key;
    int result;
    int ok = 1;

    (*p_name) = n_first;                                // start of alphabet

    if (call[0] == '\0') {
        // If call we're searching for is empty, return n_first as
        // the pointer.
        return(0);
    }

    // We create the hash key out of the lower 7 bits of the first
    // two characters, creating a 14-bit key (1 of 16384)
    //

    hash_key = (int)((call[0] & 0x7f) << 7);
    hash_key = hash_key | (int)(call[1] & 0x7f);

    // Look for a match using hash table lookup
    //
    (*p_name) = station_shortcuts[hash_key];

    if ((*p_name) == NULL) {    // No hash-table entry found.
        int mm;

        // No index found for that letter.  Walk the array until
        // we find an entry that is filled.  That'll be our
        // potential insertion point (insertion into the list will
        // occur just ahead of the hash entry).
        for (mm = hash_key+1; mm < 16384; mm++) {
            if (station_shortcuts[mm] != NULL)
            {
            	(*p_name) = station_shortcuts[mm];

                break;
            }
        }
    }


    // If we got to this point, we either have a NULL pointer or a
    // real hash-table pointer entry.  A non-NULL pointer means that
    // we have a match for the lower seven bits of the first two
    // characters of the callsign.  Check the rest of the callsign,
    // and jump out of the loop if we get outside the linear search
    // area (if first two chars are different).

    kk = (int)strlen(call);

    // Search linearly through list.  Stop at end of list or break.
    while ( (*p_name) != NULL) {

        if (exact) {
            // Check entire string for exact match
            result = strcmp( call, (*p_name)->call_sign );
        }
        else {
            // Check first part of string for match
            result = strncmp( call, (*p_name)->call_sign, kk );
        }

        if (result < 0) {   // We went past the right location.
                            // We're done.
            ok = 0;

            break;
        }
        else if (result == 0) { // Found a possible match
            break;
        }
        else {  // Result > 0.  We haven't found it yet.

        	(*p_name) = (*p_name)->n_next;  // Next element in list
        }
    }

    // Did we find anything?
    if ( (*p_name) == NULL) {
        ok = 0;
        return(ok); // Nope.  No match found.
    }

    // If "exact" is set, check that the string lengths match as
    // well.  If not, we didn't find it.
    if (exact && ok && strlen((*p_name)->call_sign) != strlen(call))
        ok = 0;

    return(ok);         // if not ok: p_name points to correct insert position in name list
}

void split_string( char *data, char *cptr[], int max ) {
  int ii;
  char *temp;
  char *current = data;


  // NULL each char pointer
  for (ii = 0; ii < max; ii++) {
    cptr[ii] = NULL;
  }

  // Save the beginning substring address
  cptr[0] = current;

  for (ii = 1; ii < max; ii++) {
    temp = strchr(current,',');  // Find next comma

    if(!temp) { // No commas found
      return; // All done with string
    }

    // Store pointer to next substring in array
    cptr[ii] = &temp[1];
    current  = &temp[1];

    // Overwrite comma with end-of-string char and bump pointer by
    // one.
    temp[0] = '\0';
  }
}

void station_shortcuts_update_function(int hash_key_in, AprsDataRow *p_rem) {
    int ii;
    AprsDataRow *ptr;
    int prev_hash_key = 0x0000;
    int hash_key;


// I just changed the function so that we can pass in the hash_key
// that we wish to update:  We should be able to speed things up by
// updating one hash key instead of all 16384 pointers.

    if ( (hash_key_in != -1)
            && (hash_key_in >= 0)
            && (hash_key_in < 16384) ) {

        // We're adding/changing a hash key entry
        station_shortcuts[hash_key_in] = p_rem;
//fprintf(stderr,"%i ",hash_key_in);
    }
    else {  // We're removing a hash key entry.

        // Clear and rebuild the entire hash table.

//??????????????????????????????????????????????????
    // Clear all of the pointers before we begin????
//??????????????????????????????????????????????????
        for (ii = 0; ii < 16384; ii++) {
            station_shortcuts[ii] = NULL;
        }

        ptr = n_first;  // Start of list


        // Loop through entire list, writing the pointer into the
        // station_shortcuts array whenever a new character is
        // encountered.  Do this until the end of the array or the end
        // of the list.
        //
        while ( (ptr != NULL) && (prev_hash_key < 16384) ) {

            // We create the hash key out of the lower 7 bits of the
            // first two characters, creating a 14-bit key (1 of 16384)
            //
            hash_key = (int)((ptr->call_sign[0] & 0x7f) << 7);
            hash_key = hash_key | (int)(ptr->call_sign[1] & 0x7f);

            if (hash_key > prev_hash_key) {

                // We found the next hash_key.  Store the pointer at the
                // correct location.
                if (hash_key < 16384) {
                    station_shortcuts[hash_key] = ptr;

                }
                prev_hash_key = hash_key;
            }
            ptr = ptr->n_next;
        }

    }

}
void init_station(AprsDataRow *p_station) {
    // the list pointers should already be set

    p_station->oldest_trackpoint  = NULL;         // no trail
    p_station->newest_trackpoint  = NULL;         // no trail
    p_station->trail_color        = 0;
    p_station->weather_data       = NULL;         // no weather
    p_station->coord_lat          = 0l;           //  90掳N  \ undefined
    p_station->coord_lon          = 0l;           // 180掳W  / position
    p_station->pos_amb            = 0;            // No ambiguity
    p_station->error_ellipse_radius = 600;        // In cm, default 6 meters
    p_station->lat_precision      = 60;           // In 100ths of seconds latitude (60 = 0.01 minutes)
    p_station->lon_precision      = 60;           // In 100ths of seconds longitude (60 = 0.01 minutes)
    p_station->call_sign[0]       = '\0';         // ?????
    p_station->tactical_call_sign = NULL;
    p_station->sec_heard          = 0;
    p_station->time_sn            = 0;
    p_station->flag               = 0;            // set all flags to inactive
    p_station->object_retransmit  = -1;           // transmit forever
    p_station->last_transmit_time = sec_now();    // Used for object/item decaying algorithm
    p_station->transmit_time_increment = 0;       // Used in data_add()
//    p_station->last_modified_time = 0;            // Used for object/item dead-reckoning
    p_station->record_type        = '\0';
    p_station->heard_via_tnc_port = 0;
    p_station->heard_via_tnc_last_time = 0;
    p_station->last_port_heard    = 0;
    p_station->num_packets        = 0;
    p_station->aprs_symbol.aprs_type = '\0';
    p_station->aprs_symbol.aprs_symbol = '\0';
    p_station->aprs_symbol.special_overlay = '\0';
    p_station->aprs_symbol.area_object.type           = AREA_NONE;
    p_station->aprs_symbol.area_object.color          = AREA_GRAY_LO;
    p_station->aprs_symbol.area_object.sqrt_lat_off   = 0;
    p_station->aprs_symbol.area_object.sqrt_lon_off   = 0;
    p_station->aprs_symbol.area_object.corridor_width = 0;
//    p_station->station_time_type  = '\0';
    p_station->origin[0]          = '\0';        // no object
    p_station->packet_time[0]     = '\0';
    p_station->node_path_ptr      = NULL;
    p_station->pos_time[0]        = '\0';
//    p_station->altitude_time[0]   = '\0';
    p_station->altitude[0]        = '\0';
//    p_station->speed_time[0]      = '\0';
    p_station->speed[0]           = '\0';
    p_station->course[0]          = '\0';
    p_station->bearing[0]         = '\0';
    p_station->NRQ[0]             = '\0';
    p_station->power_gain[0]      = '\0';
    p_station->signal_gain[0]     = '\0';
    p_station->signpost[0]        = '\0';
    p_station->probability_min[0] = '\0';
    p_station->probability_max[0] = '\0';
//    p_station->station_time[0]    = '\0';
    p_station->sats_visible[0]    = '\0';
    p_station->status_data        = NULL;
    p_station->comment_data       = NULL;
    p_station->df_color           = -1;

    // Show that there are no other points associated with this
    // station. We could also zero all the entries of the
    // multipoints[][] array, but nobody should be looking there
    // unless this is non-zero.
    // KG4NBB

    p_station->num_multipoints = 0;
    p_station->multipoint_data = NULL;
}


int tactical_keys_equal(void *key1, void *key2) {

    if (strlen((char *)key1) == strlen((char *)key2)
            && strncmp((char *)key1,(char *)key2,strlen((char *)key1))==0) {
        return(1);
    }
    else {
        return(0);
    }
}

void init_tactical_hash(int clobber) {

    // make sure we don't leak
    if (tactical_hash) {
        if (clobber) {
            hashtable_destroy(tactical_hash, 1);
            tactical_hash=create_hashtable(TACTICAL_HASH_SIZE,
                tactical_hash_from_key,
                tactical_keys_equal);
        }
    }
    else {
        tactical_hash=create_hashtable(TACTICAL_HASH_SIZE,
            tactical_hash_from_key,
            tactical_keys_equal);
    }

}

void init_weather(AprsWeatherRow *weather) {    // clear weather data

    weather->wx_sec_time             = (time_t)0;
    weather->wx_storm                = 0;
    weather->wx_time[0]              = '\0';
    weather->wx_course[0]            = '\0';
    weather->wx_speed[0]             = '\0';
    weather->wx_speed_sec_time       = 0; // ??
    weather->wx_gust[0]              = '\0';
    weather->wx_hurricane_radius[0]  = '\0';
    weather->wx_trop_storm_radius[0] = '\0';
    weather->wx_whole_gale_radius[0] = '\0';
    weather->wx_temp[0]              = '\0';
    weather->wx_rain[0]              = '\0';
    weather->wx_rain_total[0]        = '\0';
    weather->wx_snow[0]              = '\0';
    weather->wx_prec_24[0]           = '\0';
    weather->wx_prec_00[0]           = '\0';
    weather->wx_hum[0]               = '\0';
    weather->wx_baro[0]              = '\0';
    weather->wx_fuel_temp[0]         = '\0';
    weather->wx_fuel_moisture[0]     = '\0';
    weather->wx_type                 = '\0';
    weather->wx_station[0]           = '\0';
}

int is_my_call(char *call, int exact) {
    char *p_del;
    int ok;


    // U.S. special-event callsigns can be as short as three
    // characters, any less and we don't have a valid callsign.  We
    // don't check for that restriction here though.

    if (exact) {
        // We're looking for an exact match
        ok = (int)( !strcmp(call,_aprs_mycall ) );
        //fprintf(stderr,"My exact call found: %s\n",call);
    }
    else {
        // We're looking for a similar match.  Compare only up to
        // the '-' in each (if present).
        int len1,len2;

        p_del = index(call,'-');
        if (p_del == NULL)
            len1 = (int)strlen(call);
        else
            len1 = p_del - call;

        p_del = index(_aprs_mycall,'-');
        if (p_del == NULL)
            len2 = (int)strlen(_aprs_mycall);
        else
            len2 = p_del - _aprs_mycall;

        ok = (int)(len1 == len2 && !strncmp(call,_aprs_mycall,(size_t)len1));
        //fprintf(stderr,"My base call found: %s\n",call);
    }

    return(ok);
}


int is_trailpoint_echo(AprsDataRow *p_station) {
    int packets = 1;
    time_t checktime;
    AprsTrackRow *ptr;


    // Check whether we're to skip checking for dupes (reading in
    // objects/items from file is one such case).
    //
    if (skip_dupe_checking) {
        return(0);  // Say that it isn't an echo
    }

    // Start at newest end of linked list and compare.  Return if we're
    // beyond the checktime.
    ptr = p_station->newest_trackpoint;

    if (ptr == NULL)
        return(0);  // first point couldn't be an echo

    checktime = p_station->sec_heard - TRAIL_ECHO_TIME*60;

    while (ptr != NULL) {

        if (ptr->sec < checktime)
            return(0);  // outside time frame, no echo found

        if ((p_station->coord_lon == ptr->trail_long_pos)
                && (p_station->coord_lat == ptr->trail_lat_pos)
                && (p_station->speed == '\0' || ptr->speed < 0
                        || (long)(atof(p_station->speed)*18.52) == ptr->speed)
                        // current: char knots, trail: long 0.1m (-1 is undef)
                && (p_station->course == '\0' || ptr->course <= 0
                        || atoi(p_station->course) == ptr->course)
                        // current: char, trail: int (-1 is undef)
                && (p_station->altitude == '\0' || ptr->altitude <= -99999l
                        || atoi(p_station->altitude)*10 == ptr->altitude)) {
                        // current: char, trail: int (-99999l is undef)

            return(1);              // we found a delayed echo
        }
        ptr = ptr->prev;
        packets++;
    }
    return(0);                      // no echo found
}

void upd_echo(char *path) {
    int i,j,len;

    if (echo_digis[5][0] != '\0') {
        for (i=0;i<5;i++) {
            snprintf(echo_digis[i],
                MAX_CALLSIGN+1,
                "%s",
                echo_digis[i+1]);

        }
        echo_digis[5][0] = '\0';
    }
    for (i=0,j=0;i < (int)strlen(path);i++) {
        if (path[i] == '*')
            break;
        if (path[i] == ',')
            j=i;
    }
    if (j > 0)
        j++;                    // first char of call
    if (i > 0 && i-j <= 9) {
        len = i-j;
        for (i=0;i<5;i++) {     // look for free entry
            if (echo_digis[i][0] == '\0')
                break;
        }
        substr(echo_digis[i],path+j,len);
    }
}

/*from xa aprs */
int trail_color_active(int color_index) {

    // this should be made configurable...
    // to select trail colors to use

    return(1);          // accept this color
}


int new_trail_color(char *call) {
    int color, found, i;

    // If my_trail_diff_color is set a 0, then we'll
    // assign one color to every SSID from our callsign.  If
    // 1, they get the next color available (round-robin style) just
    // like all the other stations.
    //
    // 0 for last parameter in is_my_call() means skip SSID in
    // callsign check.  Non-zero means the callsign + SSID must be
    // an exact match.
    if (is_my_call(call,my_trail_diff_color)) {
        color = MY_TRAIL_COLOR;    // It's my call, so use special color
    }
    else {
        // all other callsigns get some other color out of the color table
        color = current_trail_color;
        for(i=0,found=0;!found && i<MAX_TRAIL_COLORS;i++) {
            color = (color + 1) % MAX_TRAIL_COLORS; // try next color in list
            // skip special and or inactive colors.
            if (color != MY_TRAIL_COLOR && trail_color_active(color))
                found = 1;
        }
        if (found)
            current_trail_color = color;        // save it for next time
        else
            color = current_trail_color;        // keep old color
    }
    return(color);
}

int store_trail_point(AprsDataRow *p_station,
                      long lon,
                      long lat,
                      time_t sec,
                      char *alt,
                      char *speed,
                      char *course,
                      short stn_flag) {
// TODO - trails are currently disabled
// This seems to fall over!!!
	return 1;


    char flag;
    AprsTrackRow *ptr;


    // Allocate storage for the new track point
    ptr = malloc(sizeof(AprsTrackRow));
    if (ptr == NULL) {
        return(0); // Failed due to malloc
    }

    // Check whether we have any track data saved
    if (p_station->newest_trackpoint == NULL) {
        // new trail, do initialization

        tracked_stations++;

        // Assign a new trail color 'cuz it's a new trail
        p_station->trail_color = new_trail_color(p_station->call_sign);
    }

    // Start linking the record to the new end of the chain
    ptr->prev = p_station->newest_trackpoint;   // Link to record or NULL
    ptr->next = NULL;   // Newest end of chain

    // Have an older record already?
    if (p_station->newest_trackpoint != NULL) { // Yes
        p_station->newest_trackpoint->next = ptr;
    }
    else {  // No, this is our first record
        p_station->oldest_trackpoint = ptr;
    }

    // Link it in as our newest record
    p_station->newest_trackpoint = ptr;

    ptr->trail_long_pos = lon;
    ptr->trail_lat_pos  = lat;
    ptr->sec            = sec;

    if (alt[0] != '\0')
            ptr->altitude = atoi(alt)*10;
    else
            ptr->altitude = -99999l;

    if (speed[0] != '\0')
            ptr->speed  = (long)(atof(speed)*18.52);
    else
            ptr->speed  = -1;

    if (course[0] != '\0')
            ptr->course = (int)(atof(course) + 0.5);    // Poor man's rounding
    else
            ptr->course = -1;

    flag = '\0';                    // init flags

    if ((stn_flag & ST_DIRECT) != 0)
            flag |= TR_LOCAL;           // set "local" flag

    if (ptr->prev != NULL) {    // we have at least two points...
        // Check whether distance between points is too far.  We
        // must convert from degrees to the Xastir coordinate system
        // units, which are 100th of a second.
        if (    abs(lon - ptr->prev->trail_long_pos) > (trail_segment_distance * 60*60*100) ||
                abs(lat - ptr->prev->trail_lat_pos)  > (trail_segment_distance * 60*60*100) ) {

            // Set "new track" flag if there's
            // "trail_segment_distance" degrees or more between
            // points.  Originally was hard-coded to one degree, now
            // set by a slider in the timing dialog.
            flag |= TR_NEWTRK;
        }
        else {
            // Check whether trail went above our maximum time
            // between points.  If so, don't draw segment.
            if (abs(sec - ptr->prev->sec) > (trail_segment_time *60)) {

                // Set "new track" flag if long delay between
                // reception of two points.  Time is set by a slider
                // in the timing dialog.
                flag |= TR_NEWTRK;
            }
        }

        // Since we have more then 1 previous track point, ensure we don't go over the configured max
        // (_aprs_std_pos_hist)
        AprsTrackRow *ptr_tmp = ptr;
        AprsTrackRow *ptr_tmp_previous = ptr;
        gint trackCount = 0;

        while( (ptr_tmp = ptr_tmp->prev) )
        {
        	if(trackCount > _aprs_std_pos_hist)
        	{
        		fprintf(stderr, "DEBUG: Deleting old track point\n");
        		// Remove track
        		ptr_tmp->prev->next = ptr_tmp->next;
        		if(ptr_tmp->next)
        		{
        			ptr_tmp->next->prev = ptr_tmp->prev;
        		}
        		ptr_tmp_previous = ptr_tmp->prev;
        		free( ptr_tmp);
        		ptr_tmp = ptr_tmp_previous;


        	}
        	else
        	{
        		trackCount ++;
        	}

        	ptr_tmp = ptr_tmp->prev;
        }

    }
    else {
        // Set "new track" flag for first point received.
        flag |= TR_NEWTRK;
    }
    ptr->flag = flag;

    return(1);  // We succeeded
}




APRS_Symbol *id_callsign(char *call_sign, char * to_call) {
    char *ptr;
    char *id = "/aUfbYX's><OjRkv";
    char hold[MAX_CALLSIGN+1];
    int index;
    static APRS_Symbol symbol;

    symbol.aprs_symbol = '/';
    symbol.special_overlay = '\0';
    symbol.aprs_type ='/';
    ptr=strchr(call_sign,'-');
    if(ptr!=NULL)                      /* get symbol from SSID */
        if((index=atoi(ptr+1))<= 15)
            symbol.aprs_symbol = id[index];

    if (strncmp(to_call, "GPS", 3) == 0 || strncmp(to_call, "SPC", 3) == 0 || strncmp(to_call, "SYM", 3) == 0)
    {
        substr(hold, to_call+3, 3);
        if ((ptr = strpbrk(hold, "->,")) != NULL)
            *ptr = '\0';

        if (strlen(hold) >= 2) {
            switch (hold[0]) {
                case 'A':
                    symbol.aprs_type = '\\';

                case 'P':
                    if (('0' <= hold[1] && hold[1] <= '9') || ('A' <= hold[1] && hold[1] <= 'Z'))
                        symbol.aprs_symbol = hold[1];

                    break;

                case 'O':
                    symbol.aprs_type = '\\';

                case 'B':
                    switch (hold[1]) {
                        case 'B':
                            symbol.aprs_symbol = '!';
                            break;
                        case 'C':
                            symbol.aprs_symbol = '"';
                            break;
                        case 'D':
                            symbol.aprs_symbol = '#';
                            break;
                        case 'E':
                            symbol.aprs_symbol = '$';
                            break;
                        case 'F':
                            symbol.aprs_symbol = '%';
                            break;
                        case 'G':
                            symbol.aprs_symbol = '&';
                            break;
                        case 'H':
                            symbol.aprs_symbol = '\'';
                            break;
                        case 'I':
                            symbol.aprs_symbol = '(';
                            break;
                        case 'J':
                            symbol.aprs_symbol = ')';
                            break;
                        case 'K':
                            symbol.aprs_symbol = '*';
                            break;
                        case 'L':
                            symbol.aprs_symbol = '+';
                            break;
                        case 'M':
                            symbol.aprs_symbol = ',';
                            break;
                        case 'N':
                            symbol.aprs_symbol = '-';
                            break;
                        case 'O':
                            symbol.aprs_symbol = '.';
                            break;
                        case 'P':
                            symbol.aprs_symbol = '/';
                            break;
                    }
                    break;

                case 'D':
                    symbol.aprs_type = '\\';

                case 'H':
                    switch (hold[1]) {
                        case 'S':
                            symbol.aprs_symbol = '[';
                            break;
                        case 'T':
                            symbol.aprs_symbol = '\\';
                            break;
                        case 'U':
                            symbol.aprs_symbol = ']';
                            break;
                        case 'V':
                            symbol.aprs_symbol = '^';
                            break;
                        case 'W':
                            symbol.aprs_symbol = '_';
                            break;
                        case 'X':
                            symbol.aprs_symbol = '`';
                            break;
                    }
                    break;

                case 'N':
                    symbol.aprs_type = '\\';

                case 'M':
                    switch (hold[1]) {
                        case 'R':
                            symbol.aprs_symbol = ':';
                            break;
                        case 'S':
                            symbol.aprs_symbol = ';';
                            break;
                        case 'T':
                            symbol.aprs_symbol = '<';
                            break;
                        case 'U':
                            symbol.aprs_symbol = '=';
                            break;
                        case 'V':
                            symbol.aprs_symbol = '>';
                            break;
                        case 'W':
                            symbol.aprs_symbol = '?';
                            break;
                        case 'X':
                            symbol.aprs_symbol = '@';
                            break;
                    }
                    break;

                case 'Q':
                    symbol.aprs_type = '\\';

                case 'J':
                    switch (hold[1]) {
                        case '1':
                            symbol.aprs_symbol = '{';
                            break;
                        case '2':
                            symbol.aprs_symbol = '|';
                            break;
                        case '3':
                            symbol.aprs_symbol = '}';
                            break;
                        case '4':
                            symbol.aprs_symbol = '~';
                            break;
                    }
                    break;

                case 'S':
                    symbol.aprs_type = '\\';

                case 'L':
                    if ('A' <= hold[1] && hold[1] <= 'Z')
                        symbol.aprs_symbol = tolower((int)hold[1]);

                    break;
            }
            if (hold[2]) {
                if (hold[2] >= 'a' && hold[2] <= 'j') {
                    // Compressed mode numeric overlay
                    symbol.special_overlay = hold[2] - 'a';
                }
                else if ( (hold[2] >= '0' && hold[2] <= '9')
                        || (hold[2] >= 'A' && hold[2] <= 'Z') ) {
                    // Normal overlay character
                    symbol.special_overlay = hold[2];
                }
                else {
                    // Bad overlay character found
                    symbol.special_overlay = '\0';
                }
            }
            else {
                // No overlay character found
                symbol.special_overlay = '\0';
            }
        }
    }
    return(&symbol);
}

long convert_lat_s2l(char *lat) {      /* N=0掳, Ctr=90掳, S=180掳 */
    long centi_sec;
    char copy[15];
    char n[15];
    char *p;
    char offset;


    // Find the decimal point if present
    p = strstr(lat, ".");

    if (p == NULL)  // No decimal point found
        return(0l);

    offset = p - lat;   // Arithmetic on pointers
    switch (offset) {
        case 0:     // .MM[MM]N
            return(0l); // Bad, no degrees or minutes
            break;
        case 1:     // M.MM[MM]N
            return(0l); // Bad, no degrees
            break;
        case 2:     // MM.MM[MM]N
            return(0l); // Bad, no degrees
            break;
        case 3:     // DMM.MM[MM]N
            snprintf(copy,
                sizeof(copy),
                "0%s",  // Add a leading '0'
                lat);
            break;
        case 4:     // DDMM.MM[MM]N
            snprintf(copy,
                sizeof(copy),
                "%s",   // Copy verbatim
                lat);
            break;
        default:
            break;
    }

    copy[14] = '\0';
    centi_sec=0l;
    if (copy[4]=='.'
            && (   (char)toupper((int)copy[ 5])=='N'
                || (char)toupper((int)copy[ 6])=='N'
                || (char)toupper((int)copy[ 7])=='N'
                || (char)toupper((int)copy[ 8])=='N'
                || (char)toupper((int)copy[ 9])=='N'
                || (char)toupper((int)copy[10])=='N'
                || (char)toupper((int)copy[11])=='N'
                || (char)toupper((int)copy[ 5])=='S'
                || (char)toupper((int)copy[ 6])=='S'
                || (char)toupper((int)copy[ 7])=='S'
                || (char)toupper((int)copy[ 8])=='S'
                || (char)toupper((int)copy[ 9])=='S'
                || (char)toupper((int)copy[10])=='S'
                || (char)toupper((int)copy[11])=='S')) {

        substr(n, copy, 2);       // degrees
        centi_sec=atoi(n)*60*60*100;

        substr(n, copy+2, 2);     // minutes
        centi_sec += atoi(n)*60*100;

        substr(n, copy+5, 4);     // fractional minutes
        // Keep the fourth digit if present, as it resolves to 0.6
        // of a 1/100 sec resolution.  Two counts make one count in
        // the Xastir coordinate system.

        // Extend the digits to full precision by adding zeroes on
        // the end.
        strncat(n, "0000", sizeof(n) - strlen(n));

        // Get rid of the N/S character
        if (!isdigit((int)n[2]))
            n[2] = '0';
        if (!isdigit((int)n[3]))
            n[3] = '0';

        // Terminate substring at the correct digit
        n[4] = '\0';
//fprintf(stderr,"Lat: %s\n", n);

        // Add 0.5 (Poor man's rounding)
        centi_sec += (long)((atoi(n) * 0.6) + 0.5);

        if (       (char)toupper((int)copy[ 5])=='N'
                || (char)toupper((int)copy[ 6])=='N'
                || (char)toupper((int)copy[ 7])=='N'
                || (char)toupper((int)copy[ 8])=='N'
                || (char)toupper((int)copy[ 9])=='N'
                || (char)toupper((int)copy[10])=='N'
                || (char)toupper((int)copy[11])=='N') {
            centi_sec = -centi_sec;
        }

        centi_sec += 90*60*60*100;
    }
    return(centi_sec);
}

long convert_lon_s2l(char *lon) {     /* W=0掳, Ctr=180掳, E=360掳 */
    long centi_sec;
    char copy[16];
    char n[16];
    char *p;
    char offset;


    // Find the decimal point if present
    p = strstr(lon, ".");

    if (p == NULL)  // No decimal point found
        return(0l);

    offset = p - lon;   // Arithmetic on pointers
    switch (offset) {
        case 0:     // .MM[MM]N
            return(0l); // Bad, no degrees or minutes
            break;
        case 1:     // M.MM[MM]N
            return(0l); // Bad, no degrees
            break;
        case 2:     // MM.MM[MM]N
            return(0l); // Bad, no degrees
            break;
        case 3:     // DMM.MM[MM]N
            snprintf(copy,
                sizeof(copy),
                "00%s",  // Add two leading zeroes
                lon);
            break;
        case 4:     // DDMM.MM[MM]N
            snprintf(copy,
                sizeof(copy),
                "0%s",   // Add leading '0'
                lon);
            break;
        case 5:     // DDDMM.MM[MM]N
            snprintf(copy,
                sizeof(copy),
                "%s",   // Copy verbatim
                lon);
            break;
        default:
            break;
    }

    copy[15] = '\0';
    centi_sec=0l;
    if (copy[5]=='.'
            && (   (char)toupper((int)copy[ 6])=='W'
                || (char)toupper((int)copy[ 7])=='W'
                || (char)toupper((int)copy[ 8])=='W'
                || (char)toupper((int)copy[ 9])=='W'
                || (char)toupper((int)copy[10])=='W'
                || (char)toupper((int)copy[11])=='W'
                || (char)toupper((int)copy[12])=='W'
                || (char)toupper((int)copy[ 6])=='E'
                || (char)toupper((int)copy[ 7])=='E'
                || (char)toupper((int)copy[ 8])=='E'
                || (char)toupper((int)copy[ 9])=='E'
                || (char)toupper((int)copy[10])=='E'
                || (char)toupper((int)copy[11])=='E'
                || (char)toupper((int)copy[12])=='E')) {

        substr(n,copy,3);    // degrees 013
        centi_sec=atoi(n)*60*60*100;

        substr(n,copy+3,2);  // minutes 26
        centi_sec += atoi(n)*60*100;
        // 01326.66E  01326.660E

        substr(n,copy+6,4);  // fractional minutes 66E 660E or 6601
        // Keep the fourth digit if present, as it resolves to 0.6
        // of a 1/100 sec resolution.  Two counts make one count in
        // the Xastir coordinate system.

        // Extend the digits to full precision by adding zeroes on
        // the end.
        strncat(n, "0000", sizeof(n) - strlen(n));

        // Get rid of the E/W character
        if (!isdigit((int)n[2]))
            n[2] = '0';
        if (!isdigit((int)n[3]))
            n[3] = '0';

        n[4] = '\0';    // Make sure substring is terminated
//fprintf(stderr,"Lon: %s\n", n);

        // Add 0.5 (Poor man's rounding)
        centi_sec += (long)((atoi(n) * 0.6) + 0.5);

        if (       (char)toupper((int)copy[ 6])=='W'
                || (char)toupper((int)copy[ 7])=='W'
                || (char)toupper((int)copy[ 8])=='W'
                || (char)toupper((int)copy[ 9])=='W'
                || (char)toupper((int)copy[10])=='W'
                || (char)toupper((int)copy[11])=='W'
                || (char)toupper((int)copy[12])=='W') {
            centi_sec = -centi_sec;
        }

        centi_sec +=180*60*60*100;;
    }
    return(centi_sec);
}




char *get_tactical_from_hash(char *callsign) {
    char *result;

    if (callsign == NULL || *callsign == '\0') {
        fprintf(stderr,"Empty callsign passed to get_tactical_from_hash()\n");
        return(NULL);
    }

    if (!tactical_hash) {  // no table to search
//fprintf(stderr,"Creating hash table\n");
        init_tactical_hash(1); // so create one
        return NULL;
    }

//    fprintf(stderr,"   searching for %s...",callsign);

    result=hashtable_search(tactical_hash,callsign);

        if (result) {
//            fprintf(stderr,"\t\tFound it, %s, len=%d, %s\n",
//                callsign,
//                strlen(callsign),
//                result);
        } else {
//            fprintf(stderr,"\t\tNot found, %s, len=%d\n",
//                callsign,
//                strlen(callsign));
        }

    return (result);
}






int extract_RMC(AprsDataRow *p_station, char *data, char *call_sign, char *path, int *num_digits) {
    char temp_data[40]; // short term string storage, MAX_CALLSIGN, ...  ???
    char lat_s[20];
    char long_s[20];
    int ok;
    char *Substring[12];  // Pointers to substrings parsed by split_string()
    char temp_string[MAX_MESSAGE_LENGTH+1];
    char temp_char;


    // should we copy it before processing? it changes data: ',' gets substituted by '\0' !!
    ok = 0; // Start out as invalid.  If we get enough info, we change this to a 1.

    if ( (data == NULL) || (strlen(data) < 34) ) {  // Not enough data to parse position from.
        return(ok);
    }

    p_station->record_type = NORMAL_GPS_RMC;
    // Create a timestamp from the current time
    // get_time saves the time in temp_data
    snprintf(p_station->pos_time,
        sizeof(p_station->pos_time),
        "%s",
        get_time(temp_data));
    p_station->flag &= (~ST_MSGCAP);    // clear "message capable" flag

    /* check aprs type on call sign */
    p_station->aprs_symbol = *id_callsign(call_sign, path);

    // Make a copy of the incoming data.  The string passed to
    // split_string() gets destroyed.
    snprintf(temp_string,
        sizeof(temp_string),
        "%s",
        data);
    split_string(temp_string, Substring, 12);

    // The Substring[] array contains pointers to each substring in
    // the original data string.

// GPRMC,034728,A,5101.016,N,11359.464,W,000.0,284.9,110701,018.0,E*7D
//   0     1    2    3     4    5      6   7    8      9     10    11

    if (Substring[0] == NULL)   // No GPRMC string
        return(ok);

    if (Substring[1] == NULL)   // No time string
        return(ok);

    if (Substring[2] == NULL)   // No valid fix char
        return(ok);

    if (Substring[2][0] != 'A' && Substring[2][0] != 'V')
        return(ok);
// V is a warning but we can get good data still ?
// DK7IN: got no position with 'V' !

    if (Substring[3] == NULL)   // No latitude string
        return(ok);

    if (Substring[4] == NULL)   // No latitude N/S
        return(ok);

// Need to check lat_s for validity here.  Note that some GPS's put out another digit of precision
// (4801.1234) or leave one out (4801.12).  Next character after digits should be a ','

    // Count digits after the decimal point for latitude
    if (strchr(Substring[3],'.')) {
        *num_digits = strlen(Substring[3]) - (int)(strchr(Substring[3],'.') - Substring[3]) - 1;
    }
    else {
        *num_digits = 0;
    }

    temp_char = toupper((int)Substring[4][0]);

    if (temp_char != 'N' && temp_char != 'S')   // Bad N/S
        return(ok);

    snprintf(lat_s,
        sizeof(lat_s),
        "%s%c",
        Substring[3],
        temp_char);

    if (Substring[5] == NULL)   // No longitude string
        return(ok);

    if (Substring[6] == NULL)   // No longitude E/W
        return(ok);

// Need to check long_s for validity here.  Should be all digits.  Note that some GPS's put out another
// digit of precision.  (12201.1234).  Next character after digits should be a ','

    temp_char = toupper((int)Substring[6][0]);

    if (temp_char != 'E' && temp_char != 'W')   // Bad E/W
        return(ok);

    snprintf(long_s,
        sizeof(long_s),
        "%s%c",
        Substring[5],
        temp_char);

    p_station->coord_lat = convert_lat_s2l(lat_s);
    p_station->coord_lon = convert_lon_s2l(long_s);
    // If we've made it this far, We have enough for a position now!
    ok = 1;

    // Now that we have a basic position, let's see what other data
    // can be parsed from the packet.  The rest of it can still be
    // corrupt, so we're proceeding carefully under yellow alert on
    // impulse engines only.

// GPRMC,034728,A,5101.016,N,11359.464,W,000.0,284.9,110701,018.0,E*7D
//   0     1    2    3     4    5      6   7    8      9     10    11

    if (Substring[7] == NULL) { // No speed string
        p_station->speed[0] = '\0'; // No speed available
        return(ok);
    }
    else {
        snprintf(p_station->speed,
            MAX_SPEED,
            "%s",
            Substring[7]);
        // Is it always knots, otherwise we need a conversion!
    }

    if (Substring[8] == NULL) { // No course string
        snprintf(p_station->course,
            sizeof(p_station->course),
            "000.0");  // No course available
        return(ok);
    }
    else {
        snprintf(p_station->course,
            MAX_COURSE,
            "%s",
            Substring[8]);
    }

    return(ok);
}
static void clear_area(AprsDataRow *p_station) {
    p_station->aprs_symbol.area_object.type           = AREA_NONE;
    p_station->aprs_symbol.area_object.color          = AREA_GRAY_LO;
    p_station->aprs_symbol.area_object.sqrt_lat_off   = 0;
    p_station->aprs_symbol.area_object.sqrt_lon_off   = 0;
    p_station->aprs_symbol.area_object.corridor_width = 0;
}

void overlay_symbol(char symbol, char data, AprsDataRow *fill) {

    if ( data != '/' && data !='\\') {  // Symbol overlay

        if (data >= 'a' && data <= 'j') {
            // Found a compressed posit numerical overlay
            data = data - 'a'+'0';  // Convert to a digit
        }
        if ( (data >= '0' && data <= '9')
                || (data >= 'A' && data <= 'Z') ) {
            // Found normal overlay character
            fill->aprs_symbol.aprs_type = '\\';
            fill->aprs_symbol.special_overlay = data;
        }
        else {
            // Bad overlay character.  Don't use it.  Insert the
            // normal alternate table character instead.
            fill->aprs_symbol.aprs_type = '\\';
            fill->aprs_symbol.special_overlay='\0';
        }
    }
    else {    // No overlay character
        fill->aprs_symbol.aprs_type = data;
        fill->aprs_symbol.special_overlay='\0';
    }
    fill->aprs_symbol.aprs_symbol = symbol;
}


int extract_time(AprsDataRow *p_station, char *data, int type) {
    int len, i;
    int ok = 0;

    // todo: better check of time data ranges
    len = (int)strlen(data);
    if (type == APRS_WX2) {
        // 8 digit time from stand-alone positionless weather stations...
        if (len > 8) {
            // MMDDHHMM   zulu time
            // MM 01-12         todo: better check of time data ranges
            // DD 01-31
            // HH 01-23
            // MM 01-59
            ok = 1;
            for (i=0;ok && i<8;i++)
                if (!isdigit((int)data[i]))
                    ok = 0;
            if (ok) {
//                substr(p_station->station_time,data+2,6);
//                p_station->station_time_type = 'z';
                for (i=0;i<=len-8;i++)         // delete time from data
                    data[i] = data[i+8];
            }
        }
    }
    else {
        if (len > 6) {
            // Status messages only with optional zulu format
            // DK7IN: APRS ref says one of 'z' '/' 'h', but I found 'c' at HB9TJM-8   ???
            if (toupper(data[6])=='Z' || data[6]=='/' || toupper(data[6])=='H')
                ok = 1;
            for (i=0;ok && i<6;i++)
                if (!isdigit((int)data[i]))
                    ok = 0;
            if (ok) {
//                substr(p_station->station_time,data,6);
//                p_station->station_time_type = data[6];
                for (i=0;i<=len-7;i++)         // delete time from data
                    data[i] = data[i+7];
            }
        }
    }
    return(ok);
}


int extract_GLL(AprsDataRow *p_station,char *data,char *call_sign, char *path, int *num_digits) {
    char temp_data[40]; // short term string storage, MAX_CALLSIGN, ...  ???
    char lat_s[20];
    char long_s[20];
    int ok;
    char *Substring[7];  // Pointers to substrings parsed by split_string()
    char temp_string[MAX_MESSAGE_LENGTH+1];
    char temp_char;


    ok = 0; // Start out as invalid.  If we get enough info, we change this to a 1.

    if ( (data == NULL) || (strlen(data) < 28) )  // Not enough data to parse position from.
        return(ok);

    p_station->record_type = NORMAL_GPS_GLL;
    // Create a timestamp from the current time
    // get_time saves the time in temp_data
    snprintf(p_station->pos_time,
        sizeof(p_station->pos_time),
        "%s",
        get_time(temp_data));
    p_station->flag &= (~ST_MSGCAP);    // clear "message capable" flag

    /* check aprs type on call sign */
    p_station->aprs_symbol = *id_callsign(call_sign, path);

    // Make a copy of the incoming data.  The string passed to
    // split_string() gets destroyed.
    snprintf(temp_string,
        sizeof(temp_string),
        "%s",
        data);
    split_string(temp_string, Substring, 7);

    // The Substring[] array contains pointers to each substring in
    // the original data string.

    if (Substring[0] == NULL)  // No GPGGA string
        return(ok);

    if (Substring[1] == NULL)  // No latitude string
        return(ok);

    if (Substring[2] == NULL)   // No N/S string
        return(ok);

    if (Substring[3] == NULL)   // No longitude string
        return(ok);

    if (Substring[4] == NULL)   // No E/W string
        return(ok);

    temp_char = toupper((int)Substring[2][0]);
    if (temp_char != 'N' && temp_char != 'S')
        return(ok);

    snprintf(lat_s,
        sizeof(lat_s),
        "%s%c",
        Substring[1],
        temp_char);
// Need to check lat_s for validity here.  Note that some GPS's put out another digit of precision
// (4801.1234).  Next character after digits should be a ','

    // Count digits after the decimal point for latitude
    if (strchr(Substring[1],'.')) {
        *num_digits = strlen(Substring[1]) - (int)(strchr(Substring[1],'.') - Substring[1]) - 1;
    }
    else {
        *num_digits = 0;
    }

    temp_char = toupper((int)Substring[4][0]);
    if (temp_char != 'E' && temp_char != 'W')
        return(ok);

    snprintf(long_s,
        sizeof(long_s),
        "%s%c",
        Substring[3],
        temp_char);
// Need to check long_s for validity here.  Should be all digits.  Note that some GPS's put out another
// digit of precision.  (12201.1234).  Next character after digits should be a ','

    p_station->coord_lat = convert_lat_s2l(lat_s);
    p_station->coord_lon = convert_lon_s2l(long_s);
    ok = 1; // We have enough for a position now

    snprintf(p_station->course,
        sizeof(p_station->course),
        "000.0");  // Fill in with dummy values
    p_station->speed[0] = '\0';        // Fill in with dummy values

    // A is valid, V is a warning but we can get good data still?
    // We don't currently check the data valid flag.

    return(ok);
}



gdouble
aprs_calculate_bearing(gdouble lat1, gdouble lon1, gdouble lat2, gdouble lon2)
{
    gdouble x, y;
    gdouble dlon = deg2rad(lon2 - lon1);
    lat1 = deg2rad(lat1);
    lat2 = deg2rad(lat2);

    y = sin(dlon) * cos(lat2);
    x = (cos(lat1) * sin(lat2)) - (sin(lat1) * cos(lat2) * cos(dlon));

    dlon = rad2deg(atan2(y, x));
    if(dlon < 0.0)
        dlon += 360.0;
    return dlon;
}
void remove_name(AprsDataRow *p_rem) {      // todo: return pointer to next element
    int update_shortcuts = 0;
    int hash_key;   // We use a 14-bit hash key


    // Do a quick check to see if we're removing a station record
    // that is pointed to by our pointer shortcuts array.
    // If so, update our pointer shortcuts after we're done.
    //
    // We create the hash key out of the lower 7 bits of the first
    // two characters, creating a 14-bit key (1 of 16384)
    //
    hash_key = (int)((p_rem->call_sign[0] & 0x7f) << 7);
    hash_key = hash_key | (int)(p_rem->call_sign[1] & 0x7f);

    if (station_shortcuts[hash_key] == p_rem) {
        // Yes, we're trying to remove a record that a hash key
        // directly points to.  We'll need to redo that hash key
        // after we remove the record.
        update_shortcuts++;
    }


    // Proceed to the station record removal
    //
    if (p_rem->n_prev == NULL) { // Appears to be first element in list

        if (n_first == p_rem) {  // Yes, head of list

            // Make list head point to 2nd element in list (or NULL)
            // so that we can delete the current record.
            n_first = p_rem->n_next;
        }
        else {  // No, not first element in list.  Problem!  The
                // list pointers are inconsistent for some reason.
                // The chain has been broken and we have dangling
                // pointers.

            fprintf(stderr,
                "remove_name(): ERROR: p->n_prev == NULL but p != n_first\n");

abort();    // Cause a core dump at this point
// Perhaps we could do some repair to the list pointers here?  Start
// at the other end of the chain and navigate back to this end, then
// fix up n_first to point to it?  This is at the risk of a memory
// leak, but at least Xastir might continue to run.

        }
    }
    else {  // Not the first element in the list.  Fix up pointers
            // to skip the current record.
        p_rem->n_prev->n_next = p_rem->n_next;
    }


    if (p_rem->n_next == NULL) { // Appears to be last element in list

        if (n_last == p_rem) {   // Yes, tail of list

            // Make list tail point to previous element in list (or
            // NULL) so that we can delete the current record.
            n_last = p_rem->n_prev;
        }
        else {  // No, not last element in list.  Problem!  The list
                // pointers are inconsistent for some reason.  The
                // chain has been broken and we have dangling
                // pointers.

            fprintf(stderr,
                "remove_name(): ERROR: p->n_next == NULL but p != n_last\n");

abort();    // Cause a core dump at this point
// Perhaps we could do some repair to the list pointers here?  Start
// at the other end of the chain and navigate back to this end, then
// fix up n_last to point to it?  This is at the risk of a memory
// leak, but at least Xastir might continue to run.

        }
    }
    else {  // Not the last element in the list.  Fix up pointers to
            // skip the current record.
        p_rem->n_next->n_prev = p_rem->n_prev;
    }


    // Update our pointer shortcuts.  Pass the removed hash_key to
    // the function so that we can try to redo just that hash_key
    // pointer.
    if (update_shortcuts) {
//fprintf(stderr,"\t\t\t\t\t\tRemoval of hash key: %i\n", hash_key);

        // The -1 tells the function to redo all of the hash table
        // pointers because we deleted one of them.  Later we could
        // optimize this so that only the specific pointer is fixed
        // up.
        station_shortcuts_update_function(-1, NULL);
    }


}


void delete_station_memory(AprsDataRow *p_del) {
    if (p_del == NULL)
        return;

    remove_name(p_del);
    remove_time(p_del);
    free(p_del);
    station_count--;
}
void insert_name(AprsDataRow *p_new, AprsDataRow *p_name) {

    // Set up pointer to next record (or NULL), sorted by name
    p_new->n_next = p_name;

    if (p_name == NULL) {       // Add to end of list

        p_new->n_prev = n_last;

        if (n_last == NULL)     // If we have an empty list
            n_first = p_new;    // Add it to the head of the list

        else    // List wasn't empty, add to the end of the list.
            n_last->n_next = p_new;

        n_last = p_new;
    }

    else {  // Insert new record ahead of p_name record

        p_new->n_prev = p_name->n_prev;

        if (p_name->n_prev == NULL)     // add to begin of list
            n_first = p_new;
        else
            p_name->n_prev->n_next = p_new;

        p_name->n_prev = p_new;
    }
}

AprsDataRow *insert_new_station(AprsDataRow *p_name, AprsDataRow *p_time) {
    AprsDataRow *p_new;


    p_new = (AprsDataRow *)malloc(sizeof(AprsDataRow));

    if (p_new != NULL) {                // we really got the memory
        p_new->call_sign[0] = '\0';     // just to be sure
        p_new->n_next = NULL;
        p_new->n_prev = NULL;
        p_new->t_newer = NULL;
        p_new->t_older = NULL;
        insert_name(p_new,p_name);      // insert element into name ordered list
        insert_time(p_new,p_time);      // insert element into time ordered list
    }
    else {  // p_new == NULL
        fprintf(stderr,"ERROR: we got no memory for station storage\n");
    }


    return(p_new);                      // return pointer to new element
}

char *remove_trailing_asterisk(char *data)
{
    int i;

    if (data == NULL)
        return NULL;

    if (strlen(data) == 0)
        return NULL;

// Should the test here be i>=0 ??
    for(i=strlen(data)-1;i>0;i--) {
        if(data[i] == '*')
            data[i] = '\0';
    }
    return(data);
}


void compute_rain_hour(float rain_total) {
    int current, j;
    float lowest;


    // Deposit the _starting_ rain_total for each minute into a separate bucket.
    // Subtract lowest rain_total in queue from current rain_total to get total
    // for the last hour.


    current = get_minutes(); // Fetch the current minute value.  Use this as an index
                        // into our minute "buckets" where we store the data.


    rain_minute[ (current + 1) % 60 ] = 0.0;   // Zero out the next bucket (probably have data in
                                                // there from the previous hour).


    if (rain_minute[current] == 0.0)           // If no rain_total stored yet in this minute's bucket
        rain_minute[current] = rain_total;      // Write into current bucket


    // Find the lowest non-zero value for rain_total.  The max value is "rain_total".
    lowest = rain_total;                    // Set to maximum to get things going
    for (j = 0; j < 60; j++) {
        if ( (rain_minute[j] > 0.0) && (rain_minute[j] < lowest) ) { // Found a lower non-zero value?
            lowest = rain_minute[j];        // Snag it
        }
    }
    // Found it, subtract the two to get total for the last hour
    rain_minute_total = rain_total - lowest;
}

void compute_rain(float rain_total) {
    int current, i;
    float lower;


    // Skip the routine if input is outlandish (Negative value, zero, or 512 inches!).
    // We seem to get occasional 0.0 packets from wx200d.  This makes them go away.
    if ( (rain_total <= 0.0) || (rain_total > 51200.0) )
        return;

    compute_rain_hour(rain_total);

    current = get_hours();

    // Set rain_base:  The first rain_total for each hour.
    if (rain_base[current] == 0.0) {       // If we don't have a start value yet for this hour,
        rain_base[current] = rain_total;    // save it away.
        rain_check = 0;                     // Set up rain_check so we'll do the following
                                            // "else" clause a few times at start of each hour.
    }
    else {  // rain_base has been set, is it wrong?  We recheck three times at start of hour.
        if (rain_check < 3) {
            rain_check++;
            // Is rain less than base?  It shouldn't be.
            if (rain_total < rain_base[current])
                rain_base[current] = rain_total;

            // Difference greater than 10 inches since last reading?  It shouldn't be.
            if (fabs(rain_total - rain_base[current]) > 1000.0) // Remember:  Hundredths of an inch
                rain_base[current] = rain_total;
        }
    }
    rain_base[ (current + 1) % 24 ] = 0.0;    // Clear next hour's index.


    // Compute total rain in last 24 hours:  Really we'll compute the total rain
    // in the last 23 hours plus the portion of an hour we've completed (Sum up
    // all 24 of the hour totals).  This isn't the perfect way to do it, but to
    // really do it right we'd need finer increments of time (to get closer to
    // the goal of 24 hours of rain).
    lower = rain_total;
    for ( i = 0; i < 24; i++ ) {    // Find the lowest non-zero rain_base value in last 24 hours
        if ( (rain_base[i] > 0.0) && (rain_base[i] < lower) ) {
            lower = rain_base[i];
        }
    }
    rain_24 = rain_total - lower;    // Hundredths of an inch


    // Compute rain since midnight.  Note that this uses whatever local time was set
    // on the machine.  It might not be local midnight if your box is set to GMT.
    lower = rain_total;
    for ( i = 0; i <= current; i++ ) {  // Find the lowest non-zero rain_base value since midnight
        if ( (rain_base[i] > 0.0) && (rain_base[i] < lower) ) {
            lower = rain_base[i];
        }
    }
    rain_00 = rain_total - lower;    // Hundredths of an inch

    // It is the responsibility of the calling program to save
    // the new totals in the data structure for our station.
    // We don't return anything except in global variables.

}
gdouble
aprs_calculate_distance(gdouble lat1, gdouble lon1, gdouble lat2, gdouble lon2)
{
    gdouble dlat, dlon, slat, slon, a;

    /* Convert to radians. */
    lat1 = deg2rad(lat1);
    lon1 = deg2rad(lon1);
    lat2 = deg2rad(lat2);
    lon2 = deg2rad(lon2);

    dlat = lat2 - lat1;
    dlon = lon2 - lon1;

    slat = sin(dlat / 2.0);
    slon = sin(dlon / 2.0);
    a = (slat * slat) + (cos(lat1) * cos(lat2) * slon * slon);

    return ((2.0 * atan2(sqrt(a), sqrt(1.0 - a))) * EARTH_RADIUS);
}

double distance_from_my_station(char *call_sign, gchar *course_deg, gint course_len) {
    AprsDataRow *p_station;
    double distance;
    float value;
    double d_lat, d_lon;

    distance = 0.0;
    p_station = NULL;
    if (search_station_name(&p_station,call_sign,1)) {
        // Check whether we have a posit yet for this station
        if ( (p_station->coord_lat == 0l)
                && (p_station->coord_lon == 0l) ) {
            distance = 0.0;
        }
        else {
        	d_lat = convert_lat_l2d(p_station->coord_lat);
        	d_lon = convert_lon_l2d(p_station->coord_lon);


     	//modify by nestor 修正谷歌地图

        	value = (float)aprs_calculate_distance(_gps.lat - _offset_lat, _gps.lon -  _offset_lon, d_lat, d_lon);

        	snprintf(course_deg,  course_len,
        			"%.0f°",
        			aprs_calculate_bearing(_gps.lat - _offset_lat, _gps.lon - _offset_lon, d_lat, d_lon));
      	//modify end

            if(_units == UNITS_KM)
            	distance = value * 1.852;           // nautical miles to km
            else if(_units == UNITS_MI)
                distance = value * 1.15078;         // nautical miles to miles
            else if(_units == UNITS_NM)
            	distance = value;
            else
                distance = 0.0; // Should be unreachable


        }
    }
    else {  // Station not found
        distance = 0.0;
    }


    return(distance);
}
float compute_gust(float wx_speed, float last_speed, time_t *last_speed_time) {
    float computed_gust;
    int current, j;


    // Deposit max gust for each minute into a different bucket.
    // Check all buckets for max gust within the last five minutes
    // (Really 4 minutes plus whatever portion of a minute we've completed).

    current = get_minutes(); // Fetch the current minute value.  We use this as an index
                        // into our minute "buckets" where we store the data.

    // If we haven't started collecting yet, set up to do so
    if (gust_read_ptr == gust_write_ptr) {  // We haven't started yet
        gust_write_ptr = current;           // Set to write into current bucket
        gust_last_write = current;

        gust_read_ptr = current - 1;        // Set read pointer back one, modulus 60
        if (gust_read_ptr < 0)
            gust_read_ptr = 59;

        gust[gust_write_ptr] = 0.0;         // Zero the max gust
        gust[gust_read_ptr] = 0.0;          // for both buckets.

//WE7U: Debug
//gust[gust_write_ptr] = 45.9;
    }

    // Check whether we've advanced at least one minute yet
    if (current != gust_write_ptr) {        // We've advanced to a different minute
        gust_write_ptr = current;           // Start writing into a new bucket.
        gust[gust_write_ptr] = 0.0;         // Zero the new bucket

        // Check how many bins of real data we have currently.  Note that this '5' is
        // correct, as we just advanced "current" to the next minute.  We're just pulling
        // along the read_ptr behind us if we have 5 bins worth of data by now.
        if ( ((gust_read_ptr + 5) % 60) == gust_write_ptr)  // We have 5 bins of real data
            gust_read_ptr = (gust_read_ptr + 1) % 60;       // So advance the read pointer,

        // Check for really bad pointers, perhaps the weather station got
        // unplugged for a while or it's just REALLY slow at sending us data?
        // We're checking to see if gust_last_write happened in the previous
        // minute.  If not, we skipped a minute or more somewhere.
        if ( ((gust_last_write + 1) % 60) != current ) {
            // We lost some time somewhere: Reset the pointers, older gust data is
            // lost.  Start over collecting new gust data.

            gust_read_ptr = current - 1;    // Set read pointer back one, modulus 60
            if (gust_read_ptr < 0)
                gust_read_ptr = 59;

            gust[gust_read_ptr] = 0.0;
        }
        gust_last_write = current;
    }

    // Is current wind speed higher than the current minute bucket?
    if (wx_speed > gust[gust_write_ptr])
        gust[gust_write_ptr] = wx_speed;    // Save it in the bucket

    // Read the last (up to) five buckets and find the max gust
    computed_gust=gust[gust_write_ptr];
    j = gust_read_ptr;
    while (j != ((gust_write_ptr + 1) % 60) ) {
        if ( computed_gust < gust[j] )
            computed_gust = gust[j];
        j = (j + 1) % 60;
    }

    *last_speed_time = sec_now();
    return(computed_gust);
}


int process_query( /*@unused@*/ char *call_sign, /*@unused@*/ char *path,char *message,TAprsPort port, /*@unused@*/ int third_party) {
   // char temp[100];
    int ok = 0;
    float randomize;


    // Generate a random number between 0.0 and 1.0
    randomize = rand() / (float)RAND_MAX;

    // Convert to between 0 and 120 seconds
    randomize = randomize * 120.0;

    // Check for proper usage of the ?APRS? query
//
// NOTE:  We need to add support in here for the radius circle as
// listed in the spec for general queries.  Right now we respond to
// all queries, whether we're inside the circle or not.  Spec says
// this:
//
// ?Query?Lat,Long,Radius
// 1  n  1 n 1 n  1  4 Bytes
//
// i.e. ?APRS? 34.02,-117.15,0200
//
// Note leading space in latitude as its value is positive.
// Lat/long are floating point degrees.  N/E are positive, indicated
// by a leading space.  S/W are negative.  Radius is in miles
// expressed as a fixed 4-digit number in whole miles.  All stations
// inside the specified circle should respond with a position report
// and a status report.
//
    if (!ok && strncmp(message,"APRS?",5)==0) {
        //
        // Initiate a delayed transmit of our own posit.
        // UpdateTime() uses posit_next_time to decide when to
        // transmit, so we'll just muck with that.
        //
        if ( posit_next_time - sec_now() < randomize ) {
            // Skip setting it, as we'll transmit soon anyway
        }
        else {
            posit_next_time = (size_t)(sec_now() + randomize);
        }
        ok = 1;
    }
    // Check for illegal case for the ?APRS? query
    if (!ok && g_strncasecmp(message,"APRS?",5)==0) {
        ok = 1;
//        fprintf(stderr,
//            "%s just queried us with an illegal query: %s\n",
//            call_sign,
//            message),
//        fprintf(stderr,
//            "Consider sending a message, asking them to follow the spec\n");
    }

    // Check for proper usage of the ?WX? query
    if (!ok && strncmp(message,"WX?",3)==0) {
    	// WX is not supported

        ok = 1;
    }

    // Check for illegal case for the ?WX? query
    if (!ok && g_strncasecmp(message,"WX?",3)==0) {
        ok = 1;
//        fprintf(stderr,
//            "%s just queried us with an illegal query: %s\n",
//            call_sign,
//            message),
//        fprintf(stderr,
//            "Consider sending a message, asking them to follow the spec\n");
    }

    return(ok);
}











AprsDataRow *add_new_station(AprsDataRow *p_name, AprsDataRow *p_time, char *call) {
    AprsDataRow *p_new;
    int hash_key;   // We use a 14-bit hash key
    char *tactical_call;



    if (call[0] == '\0') {
        // Do nothing.  No update needed.  Callsign is empty.
        return(NULL);
    }

/*转移到 kiss_parse_packet
    if(_aprs_show_new_station_alert)
    {
	    const gchar *msg = g_strdup_printf("New station: %s", call);
	    hildon_banner_show_information(_window, NULL, msg);
    }
*/
    p_new = insert_new_station(p_name,p_time);  // allocate memory

    if (p_new == NULL) {

        // Couldn't allocate space for the station
        return(NULL);
    }

    init_station(p_new);                    // initialize new station record


    snprintf(p_new->call_sign,
        sizeof(p_new->call_sign),
        "%s",
        call);
    station_count++;



    // Do some quick checks to see if we just inserted a new hash
    // key or inserted at the beginning of a hash key (making the
    // old pointer incorrect).  If so, update our pointers to match.

    // We create the hash key out of the lower 7 bits of the first
    // two characters, creating a 14-bit key (1 of 16384)
    //
    hash_key = (int)((call[0] & 0x7f) << 7);
    hash_key = hash_key | (int)(call[1] & 0x7f);

    if (station_shortcuts[hash_key] == NULL) {
        // New hash key entry point found.  Fill in the pointer.
        station_shortcuts_update_function(hash_key, p_new);
    }
    else if (p_new->n_prev == NULL) {
        // We just inserted at the beginning of the list.  Assume
        // that we inserted at the beginning of our hash_key
        // segment.
        station_shortcuts_update_function(hash_key, p_new);
    }
    else {
        // Check whether either of the first two chars of the new
        // callsign and the previous callsign are different.  If so,
        // we need to update the hash table entry for our new record
        // 'cuz we're at the start of a new hash table entry.
        if (p_new->n_prev->call_sign[0] != call[0]
                || p_new->n_prev->call_sign[1] != call[1]) {

            station_shortcuts_update_function(hash_key, p_new);
        }
    }

    // Check whether we have a tactical call to assign to this
    // station in our tactical hash table.


    tactical_call = get_tactical_from_hash(call);


    // If tactical call found and not blank
    if (tactical_call && tactical_call[0] != '\0') {

        // Malloc some memory to hold it in the station record.
        p_new->tactical_call_sign = (char *)malloc(MAX_TACTICAL_CALL+1);
        CHECKMALLOC(p_new->tactical_call_sign);

        snprintf(p_new->tactical_call_sign,
            MAX_TACTICAL_CALL+1,
            "%s",
            tactical_call);

        //if (tactical_call[0] == '\0')
        //    fprintf(stderr,"Blank tactical call\n");
    }

    return(p_new);                      // return pointer to new element
}

char *remove_trailing_spaces(char *data) {
    int i;

    if (data == NULL)
        return NULL;

    if (strlen(data) == 0)
        return NULL;

    for(i=strlen(data)-1;i>=0;i--)
        if(data[i] == ' ')
            data[i] = '\0';
        else
            break;

        return(data);
}
void add_status(AprsDataRow *p_station, char *status_string) {
    AprsCommentRow *ptr;
    int add_it = 0;
    int len;


    len = strlen(status_string);

    // Eliminate line-end chars
    if (len > 1) {
        if ( (status_string[len-1] == '\n')
                || (status_string[len-1] == '\r') ) {
            status_string[len-1] = '\0';
        }
    }

    // Shorten it
    (void)remove_trailing_spaces(status_string);
    (void)remove_leading_spaces(status_string);

    len = strlen(status_string);

    // Check for valid pointer
    if (p_station != NULL) {

// We should probably create a new station record for this station
// if there isn't one.  This allows us to collect as much info about
// a station as we can until a posit comes in for it.  Right now we
// don't do this.  If we decide to do this in the future, we also
// need a method to find out the info about that station without
// having to click on an icon, 'cuz the symbol won't be on our map
// until we have a posit.

        //fprintf(stderr,"Station:%s\tStatus:%s\n",p_station->call_sign,status_string);

        // Check whether we have any data stored for this station
        if (p_station->status_data == NULL) {
            if (len > 0) {
                // No status stored yet and new status is non-NULL,
                // so add it to the list.
                add_it++;
            }
        }
        else {  // We have status data stored already
                // Check for an identical string
            AprsCommentRow *ptr2;
            int ii = 0;

            ptr = p_station->status_data;
            ptr2 = ptr;
            while (ptr != NULL) {

                // Note that both text_ptr and comment_string can be
                // empty strings.

                if (strcasecmp(ptr->text_ptr, status_string) == 0) {
                    // Found a matching string
                    //fprintf(stderr,"Found match:
                    //%s:%s\n",p_station->call_sign,status_string);

// Instead of updating the timestamp, we'll delete the record from
// the list and add it to the top in the code below.  Make sure to
// tweak the "ii" pointer so that we don't end up shortening the
// list unnecessarily.
                    if (ptr == p_station->status_data) {

                        // Only update the timestamp: We're at the
                        // beginning of the list already.
                        ptr->sec_heard = sec_now();

                        return; // No need to add a new record
                    }
                    else {  // Delete the record
                        AprsCommentRow *ptr3;

                        // Keep a pointer to the record
                        ptr3 = ptr;

                        // Close the chain, skipping this record
                        ptr2->next = ptr3->next;

                        // Skip "ptr" over the record we wish to
                        // delete
                        ptr = ptr3->next;

                        // Free the record
                        free(ptr3->text_ptr);
                        free(ptr3);

                        // Muck with the counter 'cuz we just
                        // deleted one record
                        ii--;
                    }
                }
                ptr2 = ptr; // Back one record
                if (ptr != NULL) {
                    ptr = ptr->next;
                }
                ii++;
            }


            // No matching string found, or new timestamp found for
            // old record.  Add it to the top of the list.
            add_it++;
            //fprintf(stderr,"No match:
            //%s:%s\n",p_station->call_sign,status_string);

            // We counted the records.  If we have more than
            // MAX_STATUS_LINES records we'll delete/free the last
            // one to make room for the next.  This keeps us from
            // storing unique status records ad infinitum for active
            // stations, limiting the total space used.
            //
            if (ii >= MAX_STATUS_LINES) {
                // We know we didn't get a match, and that our list
                // is full (as full as we want it to be).  Traverse
                // the list again, looking for ptr2->next->next ==
                // NULL.  If found, free last record and set the
                // ptr2->next pointer to NULL.
                ptr2 = p_station->status_data;
                while (ptr2->next->next != NULL) {
                    ptr2 = ptr2->next;
                }
                // At this point, we have a pointer to the last
                // record in ptr2->next.  Free it and the text
                // string in it.
                free(ptr2->next->text_ptr);
                free(ptr2->next);
                ptr2->next = NULL;
            }
        }

        if (add_it) {   // We add to the beginning so we don't have
                        // to traverse the linked list.  This also
                        // puts new records at the beginning of the
                        // list to keep them in sorted order.

            ptr = p_station->status_data;  // Save old pointer to records
            p_station->status_data = (AprsCommentRow *)malloc(sizeof(AprsCommentRow));
            CHECKMALLOC(p_station->status_data);

            p_station->status_data->next = ptr;    // Link in old records or NULL

            // Malloc the string space we'll need, attach it to our
            // new record
            p_station->status_data->text_ptr = (char *)malloc(sizeof(char) * (len+1));
            CHECKMALLOC(p_station->status_data->text_ptr);

            // Fill in the string
            snprintf(p_station->status_data->text_ptr,
                len+1,
                "%s",
                status_string);

            // Fill in the timestamp
            p_station->status_data->sec_heard = sec_now();

            //fprintf(stderr,"Station:%s\tStatus:%s\n\n",p_station->call_sign,p_station->status_data->text_ptr);
        }
    }
}


void add_comment(AprsDataRow *p_station, char *comment_string) {
    AprsCommentRow *ptr;
    int add_it = 0;
    int len;


    len = strlen(comment_string);

    // Eliminate line-end chars
    if (len > 1) {
        if ( (comment_string[len-1] == '\n')
                || (comment_string[len-1] == '\r') ) {
            comment_string[len-1] = '\0';
        }
    }

    // Shorten it
    (void)remove_trailing_spaces(comment_string);
    (void)remove_leading_spaces(comment_string);

    len = strlen(comment_string);

    // Check for valid pointer
    if (p_station != NULL) {

        // Check whether we have any data stored for this station
        if (p_station->comment_data == NULL) {
            if (len > 0) {
                // No comments stored yet and new comment is
                // non-NULL, so add it to the list.
                add_it++;
            }
        }
        else {  // We have comment data stored already
                // Check for an identical string
            AprsCommentRow *ptr2;
            int ii = 0;

            ptr = p_station->comment_data;
            ptr2 = ptr;
            while (ptr != NULL) {

                // Note that both text_ptr and comment_string can be
                // empty strings.

                if (strcasecmp(ptr->text_ptr, comment_string) == 0) {
                    // Found a matching string
//fprintf(stderr,"Found match: %s:%s\n",p_station->call_sign,comment_string);

// Instead of updating the timestamp, we'll delete the record from
// the list and add it to the top in the code below.  Make sure to
// tweak the "ii" pointer so that we don't end up shortening the
// list unnecessarily.
                    if (ptr == p_station->comment_data) {
                        // Only update the timestamp:  We're at the
                        // beginning of the list already.
                        ptr->sec_heard = sec_now();

                        return; // No need to add a new record
                    }
                    else {  // Delete the record
                        AprsCommentRow *ptr3;

                        // Keep a pointer to the record
                        ptr3 = ptr;

                        // Close the chain, skipping this record
                        ptr2->next = ptr3->next;

                        // Skip "ptr" over the record we with to
                        // delete
                        ptr = ptr3->next;

                        // Free the record
                        free(ptr3->text_ptr);
                        free(ptr3);

                        // Muck with the counter 'cuz we just
                        // deleted one record
                        ii--;
                    }
                }
                ptr2 = ptr; // Keep this pointer one record back as
                            // we progress.

                if (ptr != NULL) {
                    ptr = ptr->next;
                }

                ii++;
            }
            // No matching string found, or new timestamp found for
            // old record.  Add it to the top of the list.
            add_it++;
            //fprintf(stderr,"No match: %s:%s\n",p_station->call_sign,comment_string);

            // We counted the records.  If we have more than
            // MAX_COMMENT_LINES records we'll delete/free the last
            // one to make room for the next.  This keeps us from
            // storing unique comment records ad infinitum for
            // active stations, limiting the total space used.
            //
            if (ii >= MAX_COMMENT_LINES) {

                // We know we didn't get a match, and that our list
                // is full (as we want it to be).  Traverse the list
                // again, looking for ptr2->next->next == NULL.  If
                // found, free that last record and set the
                // ptr2->next pointer to NULL.
                ptr2 = p_station->comment_data;
                while (ptr2->next->next != NULL) {
                    ptr2 = ptr2->next;
                }
                // At this point, we have a pointer to the last
                // record in ptr2->next.  Free it and the text
                // string in it.
                free(ptr2->next->text_ptr);
                free(ptr2->next);
                ptr2->next = NULL;
            }
        }

        if (add_it) {   // We add to the beginning so we don't have
                        // to traverse the linked list.  This also
                        // puts new records at the beginning of the
                        // list to keep them in sorted order.

            ptr = p_station->comment_data;  // Save old pointer to records
            p_station->comment_data = (AprsCommentRow *)malloc(sizeof(AprsCommentRow));
            CHECKMALLOC(p_station->comment_data);

            p_station->comment_data->next = ptr;    // Link in old records or NULL

            // Malloc the string space we'll need, attach it to our
            // new record
            p_station->comment_data->text_ptr = (char *)malloc(sizeof(char) * (len+1));
            CHECKMALLOC(p_station->comment_data->text_ptr);

            // Fill in the string
            snprintf(p_station->comment_data->text_ptr,
                len+1,
                "%s",
                comment_string);

            // Fill in the timestamp
            p_station->comment_data->sec_heard = sec_now();
        }
    }
}

void decode_U2000_L (int from, unsigned char *data, AprsWeatherRow *weather) {
    time_t last_speed_time;
    float last_speed;
    float computed_gust;
    char temp_data1[10];
    char *temp_conv;
    char format;

    last_speed = 0.0;
    last_speed_time = 0;
    computed_gust = 0.0;
    format = 0;

    weather->wx_type = WX_TYPE;
    snprintf(weather->wx_station,
        sizeof(weather->wx_station),
        "U2k");

    /* get last gust speed */
    if (strlen(weather->wx_gust) > 0 && !from) {
        /* get last speed */
        last_speed = (float)atof(weather->wx_gust);
        last_speed_time = weather->wx_speed_sec_time;
    }

    // 006B 00 58
    // 00A4 00 46 01FF 380E 2755 02C1 03E8 ---- 0052 04D7    0001 007BM
    // ^       ^  ^    ^    ^         ^                      ^
    // 0       6  8    12   16        24                     40
    /* wind speed */
    if (data[0] != '-') { // '-' signifies invalid data
        substr(temp_data1,(char *)data,4);
        snprintf(weather->wx_speed,
            sizeof(weather->wx_speed),
            "%03d",
            (int)(0.5 + ((float)strtol(temp_data1,&temp_conv,16)/10.0)*0.62137));
        if (from) {
            weather->wx_speed_sec_time = sec_now();
        } else {
            /* local station */
            computed_gust = compute_gust((float)atof(weather->wx_speed),
                                        last_speed,
                                        &last_speed_time);
            weather->wx_speed_sec_time = sec_now();
            snprintf(weather->wx_gust,
                sizeof(weather->wx_gust),
                "%03d",
                (int)(0.5 + computed_gust)); // Cheater's way of rounding
        }
    } else {
        if (!from)
            weather->wx_speed[0] = 0;
    }

    /* wind direction */
    //
    // Note that the first two digits here may be 00, or may be FF
    // if a direction calibration has been entered.  We should zero
    // them.
    //
    if (data[6] != '-') { // '-' signifies invalid data
        substr(temp_data1,(char *)(data+6),2);
        temp_data1[0] = '0';
        temp_data1[1] = '0';
        snprintf(weather->wx_course,
            sizeof(weather->wx_course),
            "%03d",
            (int)(((float)strtol(temp_data1,&temp_conv,16)/256.0)*360.0));
    } else {
        snprintf(weather->wx_course,
            sizeof(weather->wx_course),
            "000");
        if (!from)
            weather->wx_course[0]=0;
    }

    /* outdoor temp */
    if (data[8] != '-') { // '-' signifies invalid data
        int temp4;

        substr(temp_data1,(char *)(data+8),4);
        temp4 = (int)strtol(temp_data1,&temp_conv,16);

        if (temp_data1[0] > '7') {  // Negative value, convert
            temp4 = (temp4 & (temp4-0x7FFF)) - 0x8000;
        }

        snprintf(weather->wx_temp,
            sizeof(weather->wx_temp),
            "%03d",
            (int)((float)((temp4<<16)/65536)/10.0));

    }
    else {
        if (!from)
            weather->wx_temp[0]=0;
    }

    /* rain total long term */
    if (data[12] != '-') { // '-' signifies invalid data
        substr(temp_data1,(char *)(data+12),4);
        snprintf(weather->wx_rain_total,
            sizeof(weather->wx_rain_total),
            "%0.2f",
            (float)strtol(temp_data1,&temp_conv,16)/100.0);
        if (!from) {
            /* local station */
            compute_rain((float)atof(weather->wx_rain_total));
            /*last hour rain */
            snprintf(weather->wx_rain,
                sizeof(weather->wx_rain),
                "%0.2f",
                rain_minute_total);
            /*last 24 hour rain */
            snprintf(weather->wx_prec_24,
                sizeof(weather->wx_prec_24),
                "%0.2f",
                rain_24);
            /* rain since midnight */
            snprintf(weather->wx_prec_00,
                sizeof(weather->wx_prec_00),
                "%0.2f",
                rain_00);
        }
    } else {
        if (!from)
            weather->wx_rain_total[0]=0;
    }

    /* baro */
    if (data[16] != '-') { // '-' signifies invalid data
        substr(temp_data1,(char *)(data+16),4);
        snprintf(weather->wx_baro,
            sizeof(weather->wx_baro),
            "%0.1f",
            (float)strtol(temp_data1,&temp_conv,16)/10.0);
    } else {
        if (!from)
            weather->wx_baro[0]=0;
    }


    /* outdoor humidity */
    if (data[24] != '-') { // '-' signifies invalid data
        substr(temp_data1,(char *)(data+24),4);
        snprintf(weather->wx_hum,
            sizeof(weather->wx_hum),
            "%03d",
            (int)((float)strtol(temp_data1,&temp_conv,16)/10.0));
    } else {
        if (!from)
            weather->wx_hum[0]=0;
    }

    /* todays rain total */
    if (data[40] != '-') { // '-' signifies invalid data
        if (from) {
            substr(temp_data1,(char *)(data+40),4);
            snprintf(weather->wx_prec_00,
                sizeof(weather->wx_prec_00),
                "%0.2f",
                (float)strtol(temp_data1,&temp_conv,16)/100.0);
        }
    } else {
        if (!from)
            weather->wx_prec_00[0] = 0;
    }
}





void decode_U2000_P(int from, unsigned char *data, AprsWeatherRow *weather) {
    time_t last_speed_time;
    float last_speed;
    float computed_gust;
    char temp_data1[10];
    char *temp_conv;
    int len;

    last_speed      = 0.0;
    last_speed_time = 0;
    computed_gust   = 0.0;
    len = (int)strlen((char *)data);

    weather->wx_type = WX_TYPE;
    snprintf(weather->wx_station,
        sizeof(weather->wx_station),
        "U2k");

    /* get last gust speed */
    if (strlen(weather->wx_gust) > 0 && !from) {
        /* get last speed */
        last_speed = (float)atof(weather->wx_gust);
        last_speed_time = weather->wx_speed_sec_time;
    }

    // $ULTW   0031 00 37 02CE  0069 ---- 0000 86A0 0001 ---- 011901CC   0000 0005
    //         ^       ^  ^     ^    ^                   ^               ^    ^
    //         0       6  8     12   16                  32              44   48

    /* wind speed peak over last 5 min */
    if (data[0] != '-') { // '-' signifies invalid data
        substr(temp_data1,(char *)data,4);
        if (from) {
            snprintf(weather->wx_gust,
                sizeof(weather->wx_gust),
                "%03d",
                (int)(0.5 + ((float)strtol(temp_data1,&temp_conv,16)/10.0)*0.62137));
            /* this may be the only wind data */
            snprintf(weather->wx_speed,
                sizeof(weather->wx_speed),
                "%03d",
                (int)(0.5 + ((float)strtol(temp_data1,&temp_conv,16)/10.0)*0.62137));
        } else {
            /* local station and may be the only wind data */
            if (len < 51) {
                snprintf(weather->wx_speed,
                    sizeof(weather->wx_speed),
                    "%03d",
                    (int)(0.5 + ((float)strtol(temp_data1,&temp_conv,16)/10.0)*0.62137));
                computed_gust = compute_gust((float)atof(weather->wx_speed),
                                        last_speed,
                                        &last_speed_time);
                weather->wx_speed_sec_time = sec_now();
                snprintf(weather->wx_gust,
                    sizeof(weather->wx_gust),
                    "%03d",
                    (int)(0.5 + computed_gust));
            }
        }
    } else {
        if (!from)
            weather->wx_gust[0] = 0;
    }

    /* wind direction */
    //
    // Note that the first two digits here may be 00, or may be FF
    // if a direction calibration has been entered.  We should zero
    // them.
    //
    if (data[6] != '-') { // '-' signifies invalid data
        substr(temp_data1,(char *)(data+6),2);
        temp_data1[0] = '0';
        temp_data1[1] = '0';
        snprintf(weather->wx_course,
            sizeof(weather->wx_course),
            "%03d",
            (int)(((float)strtol(temp_data1,&temp_conv,16)/256.0)*360.0));
    } else {
        snprintf(weather->wx_course,
            sizeof(weather->wx_course),
            "000");
        if (!from)
            weather->wx_course[0] = 0;
    }

    /* outdoor temp */
    if (data[8] != '-') { // '-' signifies invalid data
        int temp4;

        substr(temp_data1,(char *)(data+8),4);
        temp4 = (int)strtol(temp_data1,&temp_conv,16);

        if (temp_data1[0] > '7') {  // Negative value, convert
            temp4 = (temp4 & (temp4-0x7FFF)) - 0x8000;
        }

        snprintf(weather->wx_temp,
            sizeof(weather->wx_temp),
            "%03d",
            (int)((float)((temp4<<16)/65536)/10.0));
    }
    else {
        if (!from)
            weather->wx_temp[0] = 0;
    }
    /* todays rain total (on some units) */
    if ((data[44]) != '-') { // '-' signifies invalid data
        if (from) {
            substr(temp_data1,(char *)(data+44),4);
            snprintf(weather->wx_prec_00,
                sizeof(weather->wx_prec_00),
                "%0.2f",
                (float)strtol(temp_data1,&temp_conv,16)/100.0);
        }
    } else {
        if (!from)
            weather->wx_prec_00[0] = 0;
    }

    /* rain total long term */
    if (data[12] != '-') { // '-' signifies invalid data
        substr(temp_data1,(char *)(data+12),4);
        snprintf(weather->wx_rain_total,
            sizeof(weather->wx_rain_total),
            "%0.2f",
            (float)strtol(temp_data1,&temp_conv,16)/100.0);
        if (!from) {
            /* local station */
            compute_rain((float)atof(weather->wx_rain_total));
            /*last hour rain */
            snprintf(weather->wx_rain,
                sizeof(weather->wx_rain),
                "%0.2f",
                rain_minute_total);
            /*last 24 hour rain */
            snprintf(weather->wx_prec_24,
                sizeof(weather->wx_prec_24),
                "%0.2f",
                rain_24);
            /* rain since midnight */
            snprintf(weather->wx_prec_00,
                sizeof(weather->wx_prec_00),
                "%0.2f",
                rain_00);
        }
    } else {
        if (!from)
            weather->wx_rain_total[0] = 0;
    }

    /* baro */
    if (data[16] != '-') { // '-' signifies invalid data
        substr(temp_data1,(char *)(data+16),4);
        snprintf(weather->wx_baro,
            sizeof(weather->wx_baro),
            "%0.1f",
            (float)strtol(temp_data1,&temp_conv,16)/10.0);
    } else {
        if (!from)
            weather->wx_baro[0] = 0;
    }

    /* outdoor humidity */
    if (data[32] != '-') { // '-' signifies invalid data
        substr(temp_data1,(char *)(data+32),4);
        snprintf(weather->wx_hum,
            sizeof(weather->wx_hum),
            "%03d",
            (int)((float)strtol(temp_data1,&temp_conv,16)/10.0));
    } else {
        if (!from)
            weather->wx_hum[0] = 0;
    }

    /* 1 min wind speed avg */
    if (len > 48 && (data[48]) != '-') { // '-' signifies invalid data
        substr(temp_data1,(char *)(data+48),4);
        snprintf(weather->wx_speed,
            sizeof(weather->wx_speed),
            "%03d",
            (int)(0.5 + ((float)strtol(temp_data1,&temp_conv,16)/10.0)*0.62137));
        if (from) {
            weather->wx_speed_sec_time = sec_now();
        } else {
            /* local station */
            computed_gust = compute_gust((float)atof(weather->wx_speed),
                                        last_speed,
                                        &last_speed_time);
            weather->wx_speed_sec_time = sec_now();
            snprintf(weather->wx_gust,
                sizeof(weather->wx_gust),
                "%03d",
                (int)(0.5 + computed_gust));
        }
    } else {
        if (!from) {
            if (len > 48)
                weather->wx_speed[0] = 0;
        }
    }
}

void decode_Peet_Bros(int from, unsigned char *data, AprsWeatherRow *weather, int type) {
    time_t last_speed_time;
    float last_speed;
    float computed_gust;
    char temp_data1[10];
    char *temp_conv;

    last_speed    = 0.0;
    computed_gust = 0.0;
    last_speed_time = 0;

    weather->wx_type = WX_TYPE;
    snprintf(weather->wx_station,
        sizeof(weather->wx_station),
        "UII");

    // '*' = MPH
    // '#' = km/h
    //
    // #  5 0B 75 0082 0082
    // *  7 00 76 0000 0000
    //    ^ ^  ^  ^
    //            rain [1/100 inch ?]
    //         outdoor temp
    //      wind speed [mph / km/h]
    //    wind dir

    /* wind direction */
    //
    // 0x00 is N
    // 0x04 is E
    // 0x08 is S
    // 0x0C is W
    //
    substr(temp_data1,(char *)data,1);
    snprintf(weather->wx_course,
        sizeof(weather->wx_course),
        "%03d",
        (int)(((float)strtol(temp_data1,&temp_conv,16)/16.0)*360.0));

    /* get last gust speed */
    if (strlen(weather->wx_gust) > 0 && !from) {
        /* get last speed */
        last_speed = (float)atof(weather->wx_gust);
        last_speed_time = weather->wx_speed_sec_time;
    }

    /* wind speed */
    substr(temp_data1,(char *)(data+1),2);
    if (type == APRS_WX4) {     // '#'  speed in km/h, convert to mph
        snprintf(weather->wx_speed,
            sizeof(weather->wx_speed),
            "%03d",
            (int)(0.5 + (float)(strtol(temp_data1,&temp_conv,16)*0.62137)));
    } else { // type == APRS_WX6,  '*'  speed in mph
        snprintf(weather->wx_speed,
            sizeof(weather->wx_speed),
            "%03d",
            (int)(0.5 + (float)strtol(temp_data1,&temp_conv,16)));
    }

    if (from) {
        weather->wx_speed_sec_time = sec_now();
    } else {
        /* local station */
        computed_gust = compute_gust((float)atof(weather->wx_speed),
                                last_speed,
                                &last_speed_time);
        weather->wx_speed_sec_time = sec_now();
        snprintf(weather->wx_gust,
            sizeof(weather->wx_gust),
            "%03d",
            (int)(0.5 + computed_gust));
    }

    /* outdoor temp */
    if (data[3] != '-') { // '-' signifies invalid data
        int temp4;

        substr(temp_data1,(char *)(data+3),2);
        temp4 = (int)strtol(temp_data1,&temp_conv,16);

        if (temp_data1[0] > '7') {  // Negative value, convert
            temp4 = (temp4 & (temp4-0x7FFF)) - 0x8000;
        }

        snprintf(weather->wx_temp,
            sizeof(weather->wx_temp),
            "%03d",
            temp4-56);
    } else {
        if (!from)
            weather->wx_temp[0] = 0;
    }

    // Rain divided by 100 for readings in hundredth of an inch
    if (data[5] != '-') { // '-' signifies invalid data
        substr(temp_data1,(char *)(data+5),4);
        snprintf(weather->wx_rain_total,
            sizeof(weather->wx_rain_total),
            "%0.2f",
            (float)strtol(temp_data1,&temp_conv,16)/100.0);
        if (!from) {
            /* local station */
            compute_rain((float)atof(weather->wx_rain_total));
            /*last hour rain */
            snprintf(weather->wx_rain,
                sizeof(weather->wx_rain),
                "%0.2f",
                rain_minute_total);
            /*last 24 hour rain */
            snprintf(weather->wx_prec_24,
                sizeof(weather->wx_prec_24),
                "%0.2f",
                rain_24);
            /* rain since midnight */
            snprintf(weather->wx_prec_00,
                sizeof(weather->wx_prec_00),
                "%0.2f",
                rain_00);
        }
    } else {
        if (!from)
            weather->wx_rain_total[0] = 0;
    }
}
int extract_position(AprsDataRow *p_station, char **info, int type) {
    int ok;
    char temp_lat[8+1];
    char temp_lon[9+1];
    char temp_grid[8+1];
    char *my_data;
    float gridlat;
    float gridlon;
    my_data = (*info);

	//fprintf(stderr,"extract_position0\n");

	if (type != APRS_GRID){ // Not a grid
        ok = (int)(strlen(my_data) >= 18);
        ok = (int)(ok && my_data[4]=='.' && my_data[14]=='.'
            && (toupper(my_data[7]) =='N' || toupper(my_data[7]) =='S')
            && (toupper(my_data[17])=='E' || toupper(my_data[17])=='W'));
        // errors found:  [4]: X   [7]: n s   [17]: w e
        if (ok) {
            ok =             is_num_chr(my_data[0]);           // 5230.31N/01316.88E>
            ok = (int)(ok && is_num_chr(my_data[1]));          // 0123456789012345678
            ok = (int)(ok && is_num_or_sp(my_data[2]));
            ok = (int)(ok && is_num_or_sp(my_data[3]));
            ok = (int)(ok && is_num_or_sp(my_data[5]));
            ok = (int)(ok && is_num_or_sp(my_data[6]));
            ok = (int)(ok && is_num_chr(my_data[9]));
            ok = (int)(ok && is_num_chr(my_data[10]));
            ok = (int)(ok && is_num_chr(my_data[11]));
            ok = (int)(ok && is_num_or_sp(my_data[12]));
            ok = (int)(ok && is_num_or_sp(my_data[13]));
            ok = (int)(ok && is_num_or_sp(my_data[15]));
            ok = (int)(ok && is_num_or_sp(my_data[16]));

			//fprintf(stderr,"extract_position1\n");
        }

        if (ok) {
            overlay_symbol(my_data[18], my_data[8], p_station);
            p_station->pos_amb = 0;
            // spaces in latitude set position ambiguity, spaces in longitude do not matter
            // we will adjust the lat/long to the center of the rectangle of ambiguity
            if (my_data[2] == ' ') {      // nearest degree
                p_station->pos_amb = 4;
                my_data[2]  = my_data[12] = '3';
                my_data[3]  = my_data[5]  = my_data[6]  = '0';
                my_data[13] = my_data[15] = my_data[16] = '0';
            }
            else if (my_data[3] == ' ') { // nearest 10 minutes
                p_station->pos_amb = 3;
                my_data[3]  = my_data[13] = '5';
                my_data[5]  = my_data[6]  = '0';
                my_data[15] = my_data[16] = '0';
            }
            else if (my_data[5] == ' ') { // nearest minute
                p_station->pos_amb = 2;
                my_data[5]  = my_data[15] = '5';
                my_data[6]  = '0';
                my_data[16] = '0';
            }
            else if (my_data[6] == ' ') { // nearest 1/10th minute
                p_station->pos_amb = 1;
                my_data[6]  = my_data[16] = '5';
            }

            snprintf(temp_lat,
                sizeof(temp_lat),
                "%s",
                my_data);
            temp_lat[7] = toupper(my_data[7]);
            temp_lat[8] = '\0';

            snprintf(temp_lon,
                sizeof(temp_lon),
                "%s",
                my_data+9);
            temp_lon[8] = toupper(my_data[17]);
            temp_lon[9] = '\0';

			//fprintf(stderr,"extract_position2\n");
            // Callsign check here also checks SSID for an exact
            // match
//            if (!is_my_call(p_station->call_sign,1)) {      // don't change my position, I know it better...
            if ( !(is_my_station(p_station)) ) {      // don't change my position, I know it better...

                p_station->coord_lat = convert_lat_s2l(temp_lat);   // ...in case of position ambiguity
                p_station->coord_lon = convert_lon_s2l(temp_lon);
            }

            (*info) += 19;                  // delete position from comment
        }
    }
    else { // It is a grid
        // first sanity checks, need more
        ok = (int)(is_num_chr(my_data[2]));
        ok = (int)(ok && is_num_chr(my_data[3]));
        ok = (int)(ok && ((my_data[0]>='A')&&(my_data[0]<='R')));
        ok = (int)(ok && ((my_data[1]>='A')&&(my_data[1]<='R')));
        if (ok) {
            snprintf(temp_grid,
                sizeof(temp_grid),
                "%s",
                my_data);
            // this test treats >6 digit grids as 4 digit grids; >6 are uncommon.
            // the spec mentioned 4 or 6, I'm not sure >6 is even allowed.
            if ( (temp_grid[6] != ']') || (temp_grid[4] == 0) || (temp_grid[5] == 0)){
                p_station->pos_amb = 6; // 1deg lat x 2deg lon
                temp_grid[4] = 'L';
                temp_grid[5] = 'L';
            }
            else {
                p_station->pos_amb = 5; // 2.5min lat x 5min lon
                temp_grid[4] = toupper(temp_grid[4]);
                temp_grid[5] = toupper(temp_grid[5]);
            }
            // These equations came from what I read in the qgrid source code and
            // various mailing list archives.
            gridlon= (20.*((float)temp_grid[0]-65.) + 2.*((float)temp_grid[2]-48.) + 5.*((float)temp_grid[4]-65.)/60.) - 180.;
            gridlat= (10.*((float)temp_grid[1]-65.) + ((float)temp_grid[3]-48.) + 5.*(temp_grid[5]-65.)/120.) - 90.;
            // could check for my callsign here, and avoid changing it...
            p_station->coord_lat = (unsigned long)(32400000l + (360000.0 * (-gridlat)));
            p_station->coord_lon = (unsigned long)(64800000l + (360000.0 * gridlon));
            p_station->aprs_symbol.aprs_type = '/';
            p_station->aprs_symbol.aprs_symbol = 'G';
        }        // is it valid grid or not - "ok"
        // could cut off the grid square from the comment here, but why bother?
    } // is it grid or not
    return(ok);
}
int extract_weather_item(char *data, char type, int datalen, char *temp) {
    int i,ofs,found,len;


//fprintf(stderr,"%s\n",data);

    found=0;
    len = (int)strlen(data);
    for(ofs=0; !found && ofs<len-datalen; ofs++)      // search for start sequence
        if (data[ofs]==type) {
            found=1;
            if (!is_weather_data(data+ofs+1, datalen))
                found=0;
        }
    if (found) {   // ofs now points after type character
        substr(temp,data+ofs,datalen);
        for (i=ofs-1;i<len-datalen;i++)        // delete item from info field
            data[i] = data[i+datalen+1];
        if((temp[0] == ' ') || (temp[0] == '.')) {
            // found it, but it doesn't contain a value!
            // Clean up and report "not found" - PE1DNN
            temp[0] = '\0';
            found = 0;
        }
        else
        {
//                fprintf(stderr,"extract_weather_item: %s\n",temp);
        }
    }
    else
        temp[0] = '\0';
    return(found);
}





// test-extract single weather data item from information field.  In
// other words:  Does not change the input string, but does test
// whether the data is present.  Returns a 1 if found, 0 if not
// found.
//
// PE1DNN
// If the item is contained in the string but does not contain a
// value then regard the item as "not found" in the weather string.
//
int test_extract_weather_item(char *data, char type, int datalen) {
    int ofs,found,len;

    found=0;
    len = (int)strlen(data);
    for(ofs=0; !found && ofs<len-datalen; ofs++)      // search for start sequence
        if (data[ofs]==type) {
            found=1;
            if (!is_weather_data(data+ofs+1, datalen))
                found=0;
        }

    // We really should test for numbers here (with an optional
    // leading '-'), and test across the length of the substring.
    //
    if(found && ((data[ofs+1] == ' ') || (data[ofs+1] == '.'))) {
        // found it, but it doesn't contain a value!
        // report "not found" - PE1DNN
        found = 0;
    }

    //fprintf(stderr,"test_extract: %c %d\n",type,found);
    return(found);
}


int get_weather_record(AprsDataRow *fill) {    // get or create weather storage
    int ok=1;

    if (fill->weather_data == NULL) {      // new weather data, allocate storage and init
        fill->weather_data = malloc(sizeof(AprsWeatherRow));
        if (fill->weather_data == NULL) {
            fprintf(stderr,"Couldn't allocate memory in get_weather_record()\n");
            ok = 0;
        }
        else {
            init_weather(fill->weather_data);
        }
    }
    return(ok);
}

int extract_speed_course(char *info, char *speed, char *course) {
    int i,found,len;

    len = (int)strlen(info);
    found = 0;
    if (len >= 7) {
        found = 1;
        for(i=0; found && i<7; i++) {           // check data format
            if (i==3) {                         // check separator
                if (info[i]!='/')
                    found = 0;
            }
            else {
                if( !( g_ascii_isdigit(info[i])
                        || (info[i] == ' ')     // Spaces and periods are allowed.  Need these
                        || (info[i] == '.') ) ) // here so that we can get the field deleted
                    found = 0;
            }
        }
    }
    if (found) {
        substr(course,info,3);
        substr(speed,info+4,3);
        for (i=0;i<=len-7;i++)        // delete speed/course from info field
            info[i] = info[i+7];
    }
    if (!found || atoi(course) < 1) {   // course 0 means undefined
//        speed[0] ='\0';   // Don't do this!  We can have a valid
//        speed without a valid course.
        course[0]='\0';
    }
    else {  // recheck data format looking for undefined fields
        for(i=0; i<2; i++) {
            if( !(g_ascii_isdigit(speed[i]) ) )
                speed[0] = '\0';
            if( !(g_ascii_isdigit(course[i]) ) )
                course[0] = '\0';
        }
    }

    return(found);
}



// DK7IN 77
// raw weather report            in information field
// positionless weather report   in information field
// complete weather report       with lat/lon
//  see APRS Reference page 62ff
//
// Added 'F' for Fuel Temp and 'f' for Fuel Moisture in order to
// decode these two new parameters used for RAWS weather station
// objects.
//
// By the time we call this function we've already extracted any
// time/position info at the beginning of the string.
//
int extract_weather(AprsDataRow *p_station, char *data, int compr) {
    char time_data[MAX_TIME];
    char temp[5];
    int  ok = 1;
    AprsWeatherRow *weather;
    char course[4];
    char speed[4];
    int in_knots = 0;

//WE7U
// Try copying the string to a temporary string, then do some
// extractions to see if a few weather items are present?  This
// would allow us to have the weather items in any order, and if
// enough of them were present, we consider it to be a weather
// packet?  We'd need to qualify all of the data to make sure we had
// the proper number of digits for each.  The trick is to make sure
// we don't decide it's a weather packet if it's not.  We don't know
// what people might send in packets in the future.

    if (compr) {        // compressed position report
        // Look for weather data in fixed locations first
        if (strlen(data) >= 8
                && data[0] =='g' && is_weather_data(&data[1],3)
                && data[4] =='t' && is_weather_data(&data[5],3)) {

            // Snag WX course/speed from compressed position data.
            // This speed is in knots.  This assumes that we've
            // already extracted speed/course from the compressed
            // packet.  extract_comp_position() extracts
            // course/speed as well.
            snprintf(speed,
                sizeof(speed),
                "%s",
                p_station->speed);
            snprintf(course,
                sizeof(course),
                "%s",
                p_station->course);
            in_knots = 1;

            //fprintf(stderr,"Found compressed wx\n");
        }
        // Look for weather data in non-fixed locations (RAWS WX
        // Stations?)
        else if ( strlen(data) >= 8
                && test_extract_weather_item(data,'g',3)
                && test_extract_weather_item(data,'t',3) ) {

            // Snag WX course/speed from compressed position data.
            // This speed is in knots.  This assumes that we've
            // already extracted speed/course from the compressed
            // packet.  extract_comp_position() extracts
            // course/speed as well.
            snprintf(speed,
                sizeof(speed),
                "%s",
                p_station->speed);
            snprintf(course,
                sizeof(course),
                "%s",
                p_station->course);
            in_knots = 1;

            //fprintf(stderr,"Found compressed WX in non-fixed locations! %s:%s\n",
            //    p_station->call_sign,data);

        }
        else {  // No weather data found
            ok = 0;

            //fprintf(stderr,"No compressed wx\n");
        }
    }
    else {    // Look for non-compressed weather data
        // Look for weather data in defined locations first
        if (strlen(data)>=15 && data[3]=='/'
                && is_weather_data(data,3) && is_weather_data(&data[4],3)
                && data[7] =='g' && is_weather_data(&data[8], 3)
                && data[11]=='t' && is_weather_data(&data[12],3)) {    // Complete Weather Report

            // Get speed/course.  Speed is in knots.
            (void)extract_speed_course(data,speed,course);
            in_knots = 1;

            // Either one not found?  Try again.
            if ( (speed[0] == '\0') || (course[0] == '\0') ) {

                // Try to get speed/course from 's' and 'c' fields
                // (another wx format).  Speed is in mph.
                (void)extract_weather_item(data,'c',3,course); // wind direction (in degrees)
                (void)extract_weather_item(data,'s',3,speed);  // sustained one-minute wind speed (in mph)
                in_knots = 0;
            }

            //fprintf(stderr,"Found Complete Weather Report\n");
        }
        // Look for date/time and weather in fixed locations first
        else if (strlen(data)>=16
                && data[0] =='c' && is_weather_data(&data[1], 3)
                && data[4] =='s' && is_weather_data(&data[5], 3)
                && data[8] =='g' && is_weather_data(&data[9], 3)
                && data[12]=='t' && is_weather_data(&data[13],3)) { // Positionless Weather Data
//fprintf(stderr,"Found positionless wx data\n");
            // Try to snag speed/course out of first 7 bytes.  Speed
            // is in knots.
            (void)extract_speed_course(data,speed,course);
            in_knots = 1;

            // Either one not found?  Try again.
            if ( (speed[0] == '\0') || (course[0] == '\0') ) {
//fprintf(stderr,"Trying again for course/speed\n");
                // Also try to get speed/course from 's' and 'c' fields
                // (another wx format)
                (void)extract_weather_item(data,'c',3,course); // wind direction (in degrees)
                (void)extract_weather_item(data,'s',3,speed);  // sustained one-minute wind speed (in mph)
                in_knots = 0;
            }

            //fprintf(stderr,"Found weather\n");
        }
        // Look for weather data in non-fixed locations (RAWS WX
        // Stations?)
        else if (strlen (data) >= 16
                && test_extract_weather_item(data,'h',2)
                && test_extract_weather_item(data,'g',3)
                && test_extract_weather_item(data,'t',3) ) {

            // Try to snag speed/course out of first 7 bytes.  Speed
            // is in knots.
            (void)extract_speed_course(data,speed,course);
            in_knots = 1;

            // Either one not found?  Try again.
            if ( (speed[0] == '\0') || (course[0] == '\0') ) {

                // Also try to get speed/course from 's' and 'c' fields
                // (another wx format)
                (void)extract_weather_item(data,'c',3,course); // wind direction (in degrees)
                (void)extract_weather_item(data,'s',3,speed);  // sustained one-minute wind speed (in mph)
                in_knots = 0;
            }

            //fprintf(stderr,"Found WX in non-fixed locations!  %s:%s\n",
            //    p_station->call_sign,data);
        }
        else {  // No weather data found
            ok = 0;

            //fprintf(stderr,"No wx found\n");
        }
    }

    if (ok) {
        ok = get_weather_record(p_station);     // get existing or create new weather record
    }

    if (ok) {
        weather = p_station->weather_data;

        // Copy into weather speed variable.  Convert knots to mph
        // if necessary.
        if (in_knots) {
            snprintf(weather->wx_speed,
                sizeof(weather->wx_speed),
                "%03.0f",
                atoi(speed) * 1.1508);  // Convert knots to mph
        }
        else {
            // Already in mph.  Copy w/no conversion.
            snprintf(weather->wx_speed,
                sizeof(weather->wx_speed),
                "%s",
                speed);
        }

        snprintf(weather->wx_course,
            sizeof(weather->wx_course),
            "%s",
            course);

        if (compr) {        // course/speed was taken from normal data, delete that
            // fix me: we delete a potential real speed/course now
            // we should differentiate between normal and weather data in compressed position decoding...
//            p_station->speed_time[0]     = '\0';
            p_station->speed[0]          = '\0';
            p_station->course[0]         = '\0';
        }

        (void)extract_weather_item(data,'g',3,weather->wx_gust);      // gust (peak wind speed in mph in the last 5 minutes)

        (void)extract_weather_item(data,'t',3,weather->wx_temp);      // temperature (in deg Fahrenheit), could be negative

        (void)extract_weather_item(data,'r',3,weather->wx_rain);      // rainfall (1/100 inch) in the last hour

        (void)extract_weather_item(data,'p',3,weather->wx_prec_24);   // rainfall (1/100 inch) in the last 24 hours

        (void)extract_weather_item(data,'P',3,weather->wx_prec_00);   // rainfall (1/100 inch) since midnight

        if (extract_weather_item(data,'h',2,weather->wx_hum))         // humidity (in %, 00 = 100%)
                snprintf(weather->wx_hum, sizeof(weather->wx_hum), "%03d",(atoi(weather->wx_hum)+99)%100+1);

        if (extract_weather_item(data,'b',5,weather->wx_baro))  // barometric pressure (1/10 mbar / 1/10 hPascal)
            snprintf(weather->wx_baro,
                sizeof(weather->wx_baro),
                "%0.1f",
                (float)(atoi(weather->wx_baro)/10.0));

        // If we parsed a speed/course, a second 's' parameter means
        // snowfall.  Try to parse it, but only in the case where
        // we've parsed speed out of this packet already.
        if ( (speed[0] != '\0') && (course[0] != '\0') ) {
            (void)extract_weather_item(data,'s',3,weather->wx_snow);      // snowfall, inches in the last 24 hours
        }

        (void)extract_weather_item(data,'L',3,temp);                  // luminosity (in watts per square meter) 999 and below

        (void)extract_weather_item(data,'l',3,temp);                  // luminosity (in watts per square meter) 1000 and above

        (void)extract_weather_item(data,'#',3,temp);                  // raw rain counter

        (void)extract_weather_item(data,'F',3,weather->wx_fuel_temp); // Fuel Temperature in 掳F (RAWS)

        if (extract_weather_item(data,'f',2,weather->wx_fuel_moisture))// Fuel Moisture (RAWS) (in %, 00 = 100%)
            snprintf(weather->wx_fuel_moisture,
                sizeof(weather->wx_fuel_moisture),
                "%03d",
                (atoi(weather->wx_fuel_moisture)+99)%100+1);

//    extract_weather_item(data,'w',3,temp);                          // ?? text wUII

    // now there should be the name of the weather station...

        // Create a timestamp from the current time
        snprintf(weather->wx_time,
            sizeof(weather->wx_time),
            "%s",
            get_time(time_data));

        // Set the timestamp in the weather record so that we can
        // decide whether or not to "ghost" the weather data later.
        weather->wx_sec_time=sec_now();
//        weather->wx_data=1;  // we don't need this

//        case ('.'):/* skip */
//            wx_strpos+=4;
//            break;

//        default:
//            wx_done=1;
//            weather->wx_type=data[wx_strpos];
//            if(strlen(data)>wx_strpos+1)
//                snprintf(weather->wx_station,
//                    sizeof(weather->wx_station),
//                    "%s",
//                    data+wx_strpos+1);
//            break;
    }
    return(ok);
}


int extract_storm(AprsDataRow *p_station, char *data, int compr) {
    char time_data[MAX_TIME];
    int  ok = 1;
    AprsWeatherRow *weather;
    char course[4];
    char speed[4];  // Speed in knots
    char *p, *p2;


// Should probably encode the storm type in the weather object and
// print it out in plain text in the Station Info dialog.

    if ((p = strstr(data, "/TS")) != NULL) {
        // We have a Tropical Storm
//fprintf(stderr,"Tropical Storm! %s\n",data);
    }
    else if ((p = strstr(data, "/TD")) != NULL) {
        // We have a Tropical Depression
//fprintf(stderr,"Tropical Depression! %s\n",data);
    }
    else if ((p = strstr(data, "/HC")) != NULL) {
        // We have a Hurricane
//fprintf(stderr,"Hurricane! %s\n",data);
    }
    else if ((p = strstr(data, "/TY")) != NULL) {
        // We have a Typhoon
//fprintf(stderr,"Hurricane! %s\n",data);
    }
    else if ((p = strstr(data, "/ST")) != NULL) {
        // We have a Super Typhoon
//fprintf(stderr,"Hurricane! %s\n",data);
    }
    else if ((p = strstr(data, "/SC")) != NULL) {
        // We have a Severe Cyclone
//fprintf(stderr,"Hurricane! %s\n",data);
    }
    else {  // Not one of the three we're trying to decode
        ok = 0;
        return(ok);
    }

//fprintf(stderr,"\n%s\n",data);

    // Back up 7 spots to try to extract the next items
    p2 = p - 7;
    if (p2 >= data) {
        // Attempt to extract course/speed.  Speed in knots.
        if (!extract_speed_course(p2,speed,course)) {
            // No speed/course to extract
//fprintf(stderr,"No speed/course found\n");
            ok = 0;
            return(ok);
        }
    }
    else {  // Not enough characters for speed/course.  Must have
            // guessed wrong on what type of data it is.
//fprintf(stderr,"No speed/course found 2\n");
        ok = 0;
        return(ok);
    }


//fprintf(stderr,"%s\n",data);

    if (ok) {

        // If we got this far, we have speed/course and know what type
        // of storm it is.
//fprintf(stderr,"Speed: %s, Course: %s\n",speed,course);

        ok = get_weather_record(p_station);     // get existing or create new weather record
    }

    if (ok) {
//        p_station->speed_time[0]     = '\0';

        p_station->weather_data->wx_storm = 1;  // We found a storm

        // Note that speed is in knots.  If we were stuffing it into
        // "wx_speed" we'd have to convert it to MPH.
        if (strcmp(speed,"   ") != 0 && strcmp(speed,"...") != 0) {
            snprintf(p_station->speed,
                sizeof(p_station->speed),
                "%s",
                speed);
        }
        else {
            p_station->speed[0] = '\0';
        }

        if (strcmp(course,"   ") != 0 && strcmp(course,"...") != 0)
            snprintf(p_station->course,
                sizeof(p_station->course),
                "%s",
                course);
        else
            p_station->course[0] = '\0';

        weather = p_station->weather_data;

        p2++;   // Skip the description text, "/TS", "/HC", "/TD", "/TY", "/ST", or "/SC"

        // Extract the sustained wind speed in knots
        if(extract_weather_item(p2,'/',3,weather->wx_speed))
            // Convert from knots to MPH
            snprintf(weather->wx_speed,
                sizeof(weather->wx_speed),
                "%0.1f",
                (float)(atoi(weather->wx_speed)) * 1.1508);

//fprintf(stderr,"%s\n",data);

        // Extract gust speed in knots
        if (extract_weather_item(p2,'^',3,weather->wx_gust)) // gust (peak wind speed in knots)
            // Convert from knots to MPH
            snprintf(weather->wx_gust,
                sizeof(weather->wx_gust),
                "%0.1f",
                (float)(atoi(weather->wx_gust)) * 1.1508);

//fprintf(stderr,"%s\n",data);

        // Pressure is already in millibars/hPa.  No conversion
        // needed.
        if (extract_weather_item(p2,'/',4,weather->wx_baro))  // barometric pressure (1/10 mbar / 1/10 hPascal)
            snprintf(weather->wx_baro,
                sizeof(weather->wx_baro),
                "%0.1f",
                (float)(atoi(weather->wx_baro)));

//fprintf(stderr,"%s\n",data);

        (void)extract_weather_item(p2,'>',3,weather->wx_hurricane_radius); // Nautical miles

//fprintf(stderr,"%s\n",data);

        (void)extract_weather_item(p2,'&',3,weather->wx_trop_storm_radius); // Nautical miles

//fprintf(stderr,"%s\n",data);

        (void)extract_weather_item(p2,'%',3,weather->wx_whole_gale_radius); // Nautical miles

//fprintf(stderr,"%s\n",data);

        // Create a timestamp from the current time
        snprintf(weather->wx_time,
            sizeof(weather->wx_time),
            "%s",
            get_time(time_data));

        // Set the timestamp in the weather record so that we can
        // decide whether or not to "ghost" the weather data later.
        weather->wx_sec_time=sec_now();
    }
    return(ok);
}




int extract_GGA(AprsDataRow *p_station,char *data,char *call_sign, char *path, int *num_digits) {
    char temp_data[40]; // short term string storage, MAX_CALLSIGN, ...  ???
    char lat_s[20];
    char long_s[20];
    int  ok;
    char *Substring[15];  // Pointers to substrings parsed by split_string()
    char temp_string[MAX_MESSAGE_LENGTH+1];
    char temp_char;
    int  temp_num;


    ok = 0; // Start out as invalid.  If we get enough info, we change this to a 1.

    if ( (data == NULL) || (strlen(data) < 32) )  // Not enough data to parse position from.
        return(ok);

    p_station->record_type = NORMAL_GPS_GGA;
    // Create a timestamp from the current time
    // get_time saves the time in temp_data
    snprintf(p_station->pos_time,
        sizeof(p_station->pos_time),
        "%s",
        get_time(temp_data));
    p_station->flag &= (~ST_MSGCAP);    // clear "message capable" flag

    /* check aprs type on call sign */
    p_station->aprs_symbol = *id_callsign(call_sign, path);

    // Make a copy of the incoming data.  The string passed to
    // split_string() gets destroyed.
    snprintf(temp_string,
        sizeof(temp_string),
        "%s",
        data);
    split_string(temp_string, Substring, 15);

    // The Substring[] array contains pointers to each substring in
    // the original data string.


// GPGGA,hhmmss[.sss],ddmm.mm[mm],{N|S},dddmm.mm[mm],{E|W},{0-8},dd,[d]d.d,[-dddd]d.d,M,[-ddd]d.d,M,[dddd.d],[dddd][*CHK]
//   0     1              2         3        4         5        6      7     8        9     1     1     1    1        1
//                                                                                          0     1     2    3        4

    if (Substring[0] == NULL)  // No GPGGA string
        return(ok);

    if (Substring[1] == NULL)  // No time string
        return(ok);

    if (Substring[2] == NULL)   // No latitude string
        return(ok);

    if (Substring[3] == NULL)   // No latitude N/S
        return(ok);

// Need to check lat_s for validity here.  Note that some GPS's put out another digit of precision
// (4801.1234).  Next character after digits should be a ','

    // Count digits after the decimal point for latitude
    if (strchr(Substring[2],'.')) {
        *num_digits = strlen(Substring[2]) - (int)(strchr(Substring[2],'.') - Substring[2]) - 1;
    }
    else {
        *num_digits = 0;
    }

    temp_char = toupper((int)Substring[3][0]);

    if (temp_char != 'N' && temp_char != 'S')   // Bad N/S
        return(ok);

    snprintf(lat_s,
        sizeof(lat_s),
        "%s%c",
        Substring[2],
        temp_char);

    if (Substring[4] == NULL)   // No longitude string
        return(ok);

    if (Substring[5] == NULL)   // No longitude E/W
        return(ok);

// Need to check long_s for validity here.  Should be all digits.  Note that some GPS's put out another
// digit of precision.  (12201.1234).  Next character after digits should be a ','

    temp_char = toupper((int)Substring[5][0]);

    if (temp_char != 'E' && temp_char != 'W')   // Bad E/W
        return(ok);

    snprintf(long_s,
        sizeof(long_s),
        "%s%c",
        Substring[4],
        temp_char);

    p_station->coord_lat = convert_lat_s2l(lat_s);
    p_station->coord_lon = convert_lon_s2l(long_s);

    // If we've made it this far, We have enough for a position now!
    ok = 1;


    // Now that we have a basic position, let's see what other data
    // can be parsed from the packet.  The rest of it can still be
    // corrupt, so we're proceeding carefully under yellow alert on
    // impulse engines only.

    // Check for valid fix {
    if (Substring[6] == NULL
            || Substring[6][0] == '0'      // Fix quality
            || Substring[7] == NULL        // Sat number
            || Substring[8] == NULL        // hdop
            || Substring[9] == NULL) {     // Altitude in meters
        p_station->sats_visible[0] = '\0'; // Store empty sats visible
        p_station->altitude[0] = '\0';;    // Store empty altitude
        return(ok); // A field between fix quality and altitude is missing
    }

// Need to check for validity of this number.  Should be 0-12?  Perhaps a few more with WAAS, GLONASS, etc?
    temp_num = atoi(Substring[7]);
    if (temp_num < 0 || temp_num > 30) {
        return(ok); // Number of satellites not valid
    }
    else {
        // Store
        snprintf(p_station->sats_visible,
            sizeof(p_station->sats_visible),
            "%d",
            temp_num);
    }


// Check for valid number for HDOP instead of just throwing it away?


    snprintf(p_station->altitude,
        sizeof(p_station->altitude),
        "%s",
        Substring[9]); // Get altitude

// Need to check for valid altitude before conversion

    // unit is in meters, if not adjust value ???

    if (Substring[10] == NULL)  // No units for altitude
        return(ok);

    if (Substring[10][0] != 'M') {
        //fprintf(stderr,"ERROR: should adjust altitude for meters\n");
        //} else {  // Altitude units wrong.  Assume altitude bad
        p_station->altitude[0] = '\0';
    }

    return(ok);
}


static void extract_multipoints(AprsDataRow *p_station,
        char *data,
        int type,
        int remove_string) {
    // If they're in there, the multipoints start with the
    // sequence <space><rbrace><lower><digit> and end with a <lbrace>.
    // In addition, there must be no spaces in there, and there
    // must be an even number of characters (after the lead-in).

    char *p, *p2;
    int found = 0;
    char *end;
    int data_size;


    if (data == NULL) {
        return;
    }

//fprintf(stderr,"Data: %s\t\t", data);

    data_size = strlen(data);

    end = data + (strlen(data) - 7);  // 7 == 3 lead-in chars, plus 2 points

    p_station->num_multipoints = 0;

    /*
    for (p = data; !found && p <= end; ++p) {
        if (*p == ' ' && *(p+1) == RBRACE && islower((int)*(p+2)) && isdigit((int)*(p+3)) &&
                            (p2 = strchr(p+4, LBRACE)) != NULL && ((p2 - p) % 2) == 1) {
            found = 1;
        }
    }
    */

    // Start looking at the beginning of the data.

    p = data;

    // Look for the opening string.

    while (!found && p < end && (p = strstr(p, START_STR)) != NULL) {
        // The opening string was found. Check the following information.

        if (islower((int)*(p+2)) && g_ascii_isdigit(*(p+3)) && (p2 = strchr(p+4, LBRACE)) != NULL && ((p2 - p) % 2) == 1) {
            // It all looks good!

            found = 1;
        }
        else {
            // The following characters are not right. Advance and
            // look again.

            ++p;
        }
    }

    if (found) {
        long multiplier;
        double d;
        char *m_start = p;    // Start of multipoint string
        char ok = 1;

        // The second character (the lowercase) indicates additional style information,
        // such as color, line type, etc.

        p_station->style = *(p+2);

        // The third character (the digit) indicates the way the points should be
        // used. They may be used to draw a closed polygon, a series of line segments,
        // etc.

        p_station->type = *(p+3);

        // The fourth character indicates the scale of the coordinates that
        // follow. It may range from '!' to 'z'. The value represents the
        // unit of measure (1, 0.1, 0.001, etc., in degrees) used in the offsets.
        //
        // Use the following formula to convert the char to the value:
        // (10 ^ ((c - 33) / 20)) / 10000 degrees
        //
        // Finally we have to convert to Xastir units. Xastir stores coordinates
        // as hudredths of seconds. There are 360,000 of those per degree, so we
        // need to multiply by that factor so our numbers will be converted to
        // Xastir units.

        p = p + 4;

        if (*p < '!' || *p > 'z') {
            fprintf(stderr,"extract_multipoints: invalid scale character %d\n", *p);
            ok = 0; // Failure
        }
        else {

            d = (double)(*p);
            d = pow(10.0, ((d - 33) / 20)) / 10000.0 * 360000.0;
            multiplier = (long)d;

            ++p;

            // The remaining characters are in pairs. Each pair is the
            // offset lat and lon for one of the points. (The offset is
            // from the actual location of the object.) Convert each
            // character to its numeric value and save it.

            while (*p != LBRACE && p_station->num_multipoints < MAX_MULTIPOINTS) {
                // The characters are in the range '"' (34 decimal) to 'z' (122). They
                // encode values in the range -44 to +44. To convert to the correct
                // value 78 is subtracted from the character's value.

                int lat_val = *p - 78;
                int lon_val = *(p+1) - 78;

                // Check for correct values.

                if (lon_val < -44 || lon_val > 44 || lat_val < -44 || lat_val > 44) {
                    char temp[MAX_LINE_SIZE+1];
                    int i;

                    // Filter the string so we don't send strange
                    // chars to the xterm
                    for (i = 0; i < (int)strlen(data); i++) {
                        temp[i] = data[i] & 0x7f;
                        if ( (temp[i] < 0x20) || (temp[i] > 0x7e) )
                            temp[i] = ' ';
                    }
                    temp[strlen(data)] = '\0';

                    fprintf(stderr,"extract_multipoints: invalid value in (filtered) \"%s\": %d,%d\n",
                        temp,
                        lat_val,
                        lon_val);

                    p_station->num_multipoints = 0;     // forget any points we already set
                    ok = 0; // Failure to decode
                    break;
                }

                // Malloc the storage area for this if we don't have
                // it yet.
                if (p_station->multipoint_data == NULL) {

                    p_station->multipoint_data = malloc(sizeof(AprsMultipointRow));
                    if (p_station->multipoint_data == NULL) {
                        p_station->num_multipoints = 0;
                        fprintf(stderr,"Couldn't malloc AprsMultipointRow'\n");
                        return;
                    }
                }

                // Add the offset to the object's position to obtain the position of the point.
                // Note that we're working in Xastir coordinates, and in North America they
                // are exactly opposite to lat/lon (larger numbers are farther east and south).
                // An offset with a positive value means that the point should be north and/or
                // west of the object, so we have to *subtract* the offset to get the correct
                // placement in Xastir coordinates.
                // TODO: Consider what we should do in the other geographic quadrants. Should we
                // check here for the correct sign of the offset? Or should the program that
                // creates the offsets take that into account?

                p_station->multipoint_data->multipoints[p_station->num_multipoints][0]
                    = p_station->coord_lon - (lon_val * multiplier);
                p_station->multipoint_data->multipoints[p_station->num_multipoints][1]
                    = p_station->coord_lat - (lat_val * multiplier);

                p += 2;
                ++p_station->num_multipoints;
            }   // End of while loop
        }

        if (ok && remove_string) {
            // We've successfully decoded a multipoint object?
            // Remove the multipoint strings (and the sequence
            // number at the end if present) from the data string.
            // m_start points to the first character (a space).  'p'
            // should be pointing at the LBRACE character.

            // Make 'p' point to just after the end of the chars
            while ( (p < data+strlen(data)) && (*p != ' ') ) {
               p++;
            }
            // The string that 'p' points to now may be empty

            // Truncate "data" at the starting brace - 1
            *m_start = '\0';

            // Now we have two strings inside "data".  Copy the 2nd
            // string directly onto the end of the first.
            strncat(data, p, data_size+1);

            // The multipoint string and sequence number should be
            // erased now from "data".
//fprintf(stderr,"New Data: %s\n", data);
        }

    }

}


int extract_comp_position(AprsDataRow *p_station, char **info, /*@unused@*/ int type) {
    int ok;
    int x1, x2, x3, x4, y1, y2, y3, y4;
    int c = 0;
    int s = 0;
    int T = 0;
    int len;
    char *my_data;
    float lon = 0;
    float lat = 0;
    float range;
    int skip = 0;
    char L;

    my_data = (*info);

    // Check leading char.  Must be one of these:
    // '/'
    // '\'
    // A-Z
    // a-j
    //
    L = my_data[0];
    if (   L == '/'
        || L == '\\'
        || ( L >= 'A' && L <= 'Z' )
        || ( L >= 'a' && L <= 'j' ) ) {
        // We're good so far
    }
    else {
        // Note one of the symbol table or overlay characters, so
        // there's something funky about this packet.  It's not a
        // properly formatted compressed position.
        return(0);
    }

    //fprintf(stderr,"my_data: %s\n",my_data);

    // If c = space, csT bytes are ignored.  Minimum length:  8
    // bytes for lat/lon, 2 for symbol, 3 for csT for a total of 13.
    len = strlen(my_data);
    ok = (int)(len >= 13);

    if (ok) {
        y1 = (int)my_data[1] - '!';
        y2 = (int)my_data[2] - '!';
        y3 = (int)my_data[3] - '!';
        y4 = (int)my_data[4] - '!';
        x1 = (int)my_data[5] - '!';
        x2 = (int)my_data[6] - '!';
        x3 = (int)my_data[7] - '!';
        x4 = (int)my_data[8] - '!';

        // csT bytes
        if (my_data[10] == ' ') // Space
            c = -1; // This causes us to ignore csT
        else {
            c = (int)my_data[10] - '!';
            s = (int)my_data[11] - '!';
            T = (int)my_data[12] - '!';
        }
        skip = 13;

        // Convert ' ' to '0'.  Not specified in APRS Reference!  Do
        // we need it?
        if (x1 == -1) x1 = '\0';
        if (x2 == -1) x2 = '\0';
        if (x3 == -1) x3 = '\0';
        if (x4 == -1) x4 = '\0';
        if (y1 == -1) y1 = '\0';
        if (y2 == -1) y2 = '\0';
        if (y3 == -1) y3 = '\0';
        if (y4 == -1) y4 = '\0';

        ok = (int)(ok && (x1 >= '\0' && x1 < 91));  //  /YYYYXXXX$csT
        ok = (int)(ok && (x2 >= '\0' && x2 < 91));  //  0123456789012
        ok = (int)(ok && (x3 >= '\0' && x3 < 91));
        ok = (int)(ok && (x4 >= '\0' && x4 < 91));
        ok = (int)(ok && (y1 >= '\0' && y1 < 91));
        ok = (int)(ok && (y2 >= '\0' && y2 < 91));
        ok = (int)(ok && (y3 >= '\0' && y3 < 91));
        ok = (int)(ok && (y4 >= '\0' && y4 < 91));

        T &= 0x3F;      // DK7IN: force Compression Byte to valid format
                        // mask off upper two unused bits, they should be zero!?

        ok = (int)(ok && (c == -1 || ((c >=0 && c < 91) && (s >= 0 && s < 91) && (T >= 0 && T < 64))));

        if (ok) {
            lat = (((y1 * 91 + y2) * 91 + y3) * 91 + y4 ) / 380926.0; // in deg, 0:  90掳N
            lon = (((x1 * 91 + x2) * 91 + x3) * 91 + x4 ) / 190463.0; // in deg, 0: 180掳W
            lat *= 60 * 60 * 100;                       // in 1/100 sec
            lon *= 60 * 60 * 100;                       // in 1/100 sec

            // The below check should _not_ be done.  Compressed
            // format can resolve down to about 1 foot worldwide
            // (0.3 meters).
            //if ((((long)(lat+4) % 60) > 8) || (((long)(lon+4) % 60) > 8))
            //    ok = 0;   // check max resolution 0.01 min to
                            // catch even more errors
        }
    }

    if (ok) {
        overlay_symbol(my_data[9], my_data[0], p_station);      // Symbol / Table

        // Callsign check here includes checking SSID for an exact
        // match
//        if (!is_my_call(p_station->call_sign,1)) {  // don't change my position, I know it better...
        if ( !(is_my_station(p_station)) ) {  // don't change my position, I know it better...

            // Record the uncompressed lat/long that we just
            // computed.
            p_station->coord_lat = (long)((lat));               // in 1/100 sec
            p_station->coord_lon = (long)((lon));               // in 1/100 sec
        }

        if (c >= 0) {                                   // ignore csT if c = ' '
            if (c < 90) {   // Found course/speed or altitude bytes
                if ((T & 0x18) == 0x10) {   // check for GGA (with altitude)
                    snprintf(p_station->altitude, sizeof(p_station->altitude), "%06.0f",pow(1.002,(double)(c*91+s))*0.3048);
                }
                else { // Found compressed course/speed bytes

                    // Convert 0 degrees to 360 degrees so that
                    // Xastir will see it as a valid course and do
                    // dead-reckoning properly on this station
                    if (c == 0) {
                        c = 90;
                    }

                    // Compute course in degrees
                    snprintf(p_station->course,
                        sizeof(p_station->course),
                        "%03d",
                        c*4);

                    // Compute speed in knots
                    snprintf(p_station->speed,
                        sizeof(p_station->speed),
                        "%03.0f",
                        pow( 1.08,(double)s ) - 1.0);

                    //fprintf(stderr,"Decoded speed:%s, course:%s\n",p_station->speed,p_station->course);

                }
            }
            else {    // Found pre-calculated radio range bytes
                if (c == 90) {
                    // pre-calculated radio range
                    range = 2 * pow(1.08,(double)s);    // miles

                    // DK7IN: dirty hack...  but better than nothing
                    if (s <= 5)                         // 2.9387 mi
                        snprintf(p_station->power_gain, sizeof(p_station->power_gain), "PHG%s0", "000");
                    else if (s <= 17)                   // 7.40 mi
                        snprintf(p_station->power_gain, sizeof(p_station->power_gain), "PHG%s0", "111");
                    else if (s <= 36)                   // 31.936 mi
                        snprintf(p_station->power_gain, sizeof(p_station->power_gain), "PHG%s0", "222");
                    else if (s <= 75)                   // 642.41 mi
                        snprintf(p_station->power_gain, sizeof(p_station->power_gain), "PHG%s0", "333");
                    else                       // max 90:  2037.8 mi
                        snprintf(p_station->power_gain, sizeof(p_station->power_gain), "PHG%s0", "444");
                }
            }
        }
        (*info) += skip;    // delete position from comment
    }


    //fprintf(stderr,"  extract_comp_position end: %s\n",*info);

    return(ok);
}

int extract_bearing_NRQ(char *info, char *bearing, char *nrq) {
    int i,found,len;

    len = (int)strlen(info);
    found = 0;
    if (len >= 8) {
        found = 1;
        for(i=1; found && i<8; i++)         // check data format
            if(!(isdigit((int)info[i]) || (i==4 && info[i]=='/')))
                found=0;
    }
    if (found) {
        substr(bearing,info+1,3);
        substr(nrq,info+5,3);

        for (i=0;i<=len-8;i++)        // delete bearing/nrq from info field
            info[i] = info[i+8];
    }

    if (!found) {
        bearing[0] ='\0';
        nrq[0]='\0';
    }
    return(found);
}
int extract_omnidf(char *info, char *phgd) {
    int i,found,len;

    found=0;
    len = (int)strlen(info);
    if (len >= 8 && strncmp(info,"DFS",3)==0 && info[7]=='/'    // trailing '/' not defined in Reference...
                 && g_ascii_isdigit(info[3]) && g_ascii_isdigit(info[5]) && g_ascii_isdigit(info[6])) {
        substr(phgd,info,7);
        for (i=0;i<=len-8;i++)        // delete omnidf from data extension field
            info[i] = info[i+8];
        return(1);
    }
    else {
        phgd[0] = '\0';
        return(0);
    }
}



void process_data_extension(AprsDataRow *p_station, char *data, /*@unused@*/ int type) {
    char temp1[7+1];
    char temp2[3+1];
    char temp3[10+1];
    char bearing[3+1];
    char nrq[3+1];

    if (p_station->aprs_symbol.aprs_type == '\\' && p_station->aprs_symbol.aprs_symbol == 'l') {
            /* This check needs to come first because the area object extension can look
               exactly like what extract_speed_course will attempt to decode. */
            extract_area(p_station, data);
    }
    else {
        clear_area(p_station); // we got a packet with a non area symbol, so clear the data

        if (extract_speed_course(data,temp1,temp2)) {  // ... from Mic-E, etc.
        //fprintf(stderr,"extracted speed/course\n");

            if (atof(temp2) > 0) {
            //fprintf(stderr,"course is non-zero\n");
            snprintf(p_station->speed,
                sizeof(p_station->speed),
                "%06.2f",
                atof(temp1));
            snprintf(p_station->course,  // in degrees
                sizeof(p_station->course),
                "%s",
                temp2);
            }

            if (extract_bearing_NRQ(data, bearing, nrq)) {  // Beam headings from DF'ing
                //fprintf(stderr,"extracted bearing and NRQ\n");
                snprintf(p_station->bearing,
                    sizeof(p_station->bearing),
                    "%s",
                    bearing);
                snprintf(p_station->NRQ,
                    sizeof(p_station->NRQ),
                    "%s",
                    nrq);
                p_station->signal_gain[0] = '\0';   // And blank out the shgd values
            }
        }
        // Don't try to extract speed & course if a compressed
        // object.  Test for beam headings for compressed packets
        // here
        else if (extract_bearing_NRQ(data, bearing, nrq)) {  // Beam headings from DF'ing

            //fprintf(stderr,"extracted bearing and NRQ\n");
            snprintf(p_station->bearing,
                    sizeof(p_station->bearing),
                    "%s",
                    bearing);
            snprintf(p_station->NRQ,
                    sizeof(p_station->NRQ),
                    "%s",
                    nrq);
            p_station->signal_gain[0] = '\0';   // And blank out the shgd values
        }
        else {
            if (extract_powergain_range(data,temp1)) {

//fprintf(stderr,"Found power_gain: %s\n", temp1);

                snprintf(p_station->power_gain,
                    sizeof(p_station->power_gain),
                    "%s",
                    temp1);

                if (extract_bearing_NRQ(data, bearing, nrq)) {  // Beam headings from DF'ing
                    //fprintf(stderr,"extracted bearing and NRQ\n");
                    snprintf(p_station->bearing,
                        sizeof(p_station->bearing),
                        "%s",
                        bearing);
                    snprintf(p_station->NRQ,
                        sizeof(p_station->NRQ),
                        "%s",
                        nrq);
                    p_station->signal_gain[0] = '\0';   // And blank out the shgd values
                }
            }
            else {
                if (extract_omnidf(data,temp1)) {
                    snprintf(p_station->signal_gain,
                        sizeof(p_station->signal_gain),
                        "%s",
                        temp1);   // Grab the SHGD values
                    p_station->bearing[0] = '\0';   // And blank out the bearing/NRQ values
                    p_station->NRQ[0] = '\0';

                    // The spec shows speed/course before DFS, but example packets that
                    // come with DOSaprs show DFSxxxx/speed/course.  We'll take care of
                    // that possibility by trying to decode speed/course again.
                    if (extract_speed_course(data,temp1,temp2)) {  // ... from Mic-E, etc.
                    //fprintf(stderr,"extracted speed/course\n");
                        if (atof(temp2) > 0) {
                            //fprintf(stderr,"course is non-zero\n");
                            snprintf(p_station->speed,
                                sizeof(p_station->speed),
                                "%06.2f",
                                atof(temp1));
                            snprintf(p_station->course,
                                sizeof(p_station->course),
                                "%s",
                                temp2);                    // in degrees
                        }
                    }

                    // The spec shows that omnidf and bearing/NRQ can be in the same
                    // packet, which makes no sense, but we'll try to decode it that
                    // way anyway.
                    if (extract_bearing_NRQ(data, bearing, nrq)) {  // Beam headings from DF'ing
                        //fprintf(stderr,"extracted bearing and NRQ\n");
                        snprintf(p_station->bearing,
                            sizeof(p_station->bearing),
                            "%s",
                            bearing);
                        snprintf(p_station->NRQ,
                            sizeof(p_station->NRQ),
                            "%s",
                            nrq);
                        //p_station->signal_gain[0] = '\0';   // And blank out the shgd values
                    }
                }
            }
        }

        if (extract_signpost(data, temp2)) {
            //fprintf(stderr,"extracted signpost data\n");
            snprintf(p_station->signpost,
                sizeof(p_station->signpost),
                "%s",
                temp2);
        }

        if (extract_probability_min(data, temp3, sizeof(temp3))) {
            //fprintf(stderr,"extracted probability_min data: %s\n",temp3);
            snprintf(p_station->probability_min,
                sizeof(p_station->probability_min),
                "%s",
                temp3);
        }

        if (extract_probability_max(data, temp3, sizeof(temp3))) {
            //fprintf(stderr,"extracted probability_max data: %s\n",temp3);
            snprintf(p_station->probability_max,
                sizeof(p_station->probability_max),
                "%s",
                temp3);
        }
    }
}

int extract_altitude(char *info, char *altitude) {
    int i,ofs,found,len;

    found=0;
    len = (int)strlen(info);
    for(ofs=0; !found && ofs<len-8; ofs++)      // search for start sequence
        if (strncmp(info+ofs,"/A=",3)==0) {
            found=1;
            // Are negative altitudes even defined?  Yes!  In Mic-E spec to -10,000 meters
            if(!isdigit((int)info[ofs+3]) && info[ofs+3]!='-')  // First char must be digit or '-'
                found=0;
            for(i=4; found && i<9; i++)         // check data format for next 5 chars
                if(!isdigit((int)info[ofs+i]))
                    found=0;
    }
    if (found) {
        ofs--;  // was one too much on exit from for loop
        substr(altitude,info+ofs+3,6);
        for (i=ofs;i<=len-9;i++)        // delete altitude from info field
            info[i] = info[i+9];
    }
    else
        altitude[0] = '\0';
    return(found);
}

void process_info_field(AprsDataRow *p_station, char *info, /*@unused@*/ int type) {
    char temp_data[6+1];
//    char time_data[MAX_TIME];

    if (extract_altitude(info,temp_data)) {                         // get altitude
        snprintf(p_station->altitude, sizeof(p_station->altitude), "%.2f",atof(temp_data)*0.3048);
        //fprintf(stderr,"%.2f\n",atof(temp_data)*0.3048);
    }
    // do other things...
}




int data_add(gint type,
             gchar *call_sign,
             gchar *path,
             gchar *data,
             TAprsPort port,
             gchar *origin,
             gint third_party,
             gint station_is_mine,
             gint object_is_mine) {


    AprsDataRow *p_station;
    AprsDataRow *p_time;
	char *pos_info;
    char call[MAX_CALLSIGN+1];
    char new_station;
    long last_lat, last_lon;
    char last_alt[MAX_ALTITUDE];
    char last_speed[MAX_SPEED+1];
    char last_course[MAX_COURSE+1];
    time_t last_stn_sec;
    short last_flag;
    char temp_data[40]; // short term string storage, MAX_CALLSIGN, ...
//    long l_lat, l_lon;
    //double distance;
//    char station_id[600];
    int found_pos;
    //float value;
    AprsWeatherRow *weather;
    int moving;
    int changed_pos = 0;
    int screen_update = 0;
    int ok, store;
    int ok_to_display =0;
    int compr_pos;
    char *p = NULL; // KC2ELS - used for WIDEn-N
    int direct = 0;
    int object_is_mine_previous = 0;
    int new_origin_is_mine = 0;
    int num_digits = 0; // Number of digits after decimal point in NMEA string
	//fprintf(stderr,"data1:%s\n",data);
    pos_info=data;
	static unsigned long int tncr_packet_count=0;
	static unsigned long int inetr_packet_count=0;
// TODO update based on time
//    static time_t lastScreenUpdate;


    // call and path had been validated before
    // Check "data" against the max APRS length, and dump the packet if too long.
    if ( (data != NULL) && (strlen(data) > MAX_INFO_FIELD_SIZE) ) {
    	// Overly long packet.  Throw it away.

        return(0);  // Not an ok packet
    }

    // Check for some reasonable string in call_sign parameter
    if (call_sign == NULL || strlen(call_sign) == 0) {

        return(0);
    }


    if (origin && is_my_call(origin, 1)) {
        new_origin_is_mine++;   // The new object/item is owned by me
    }

    weather = NULL; // only to make the compiler happy...
    found_pos = 1;
    snprintf(call,
        sizeof(call),
        "%s",
        call_sign);
    p_station = NULL;
    new_station = (char)FALSE;                          // to make the compiler happy...
    last_lat = 0L;
    last_lon = 0L;
    last_stn_sec = sec_now();
    last_alt[0]    = '\0';
    last_speed[0]  = '\0';
    last_course[0] = '\0';
    last_flag      = 0;
    ok = 0;
    store = 0;
    p_time = NULL;                                      // add to end of time sorted list (newest)
    compr_pos = 0;


    if (search_station_name(&p_station,call,1))
    {       // If we found the station in our list


//    	fprintf(stderr, "DEBUG: Station:,Found,:%s:\n", call);

        // Check whether it's already a locally-owned object/item
        if (is_my_object_item(p_station)) {

            // We don't want to re-order it in the time-ordered list
            // so that it'll expire from the queue normally.  Don't
            // call "move_station_time()" here.

            // We need an exception later in this function for the
            // case where we've moved an object/item (by how much?).
            // We need to update the time in this case so that it'll
            // expire later (in fact it could already be expired
            // when we move it).  We should be able to move expired
            // objects/items to make them active again.  Perhaps
            // some other method as well?

            new_station = (char)FALSE;
            object_is_mine_previous++;
        }
        else {
  			//add by:nestor dispay station name
  			/*
	    if(_aprs_show_new_station_alert)
    {
	    const gchar *msg1 = g_strdup_printf("Station: %s", call);
	    hildon_banner_show_information(_window, NULL, msg1);
    }*/
		//add end

   	//fprintf(stderr, "DEBUG: Station:,Update,:%s:\n", call);
            move_station_time(p_station,p_time);        // update time, change position in time sorted list
            new_station = (char)FALSE;                  // we have seen this one before
        }

        if (is_my_station(p_station)) {
            station_is_mine++; // Station/object/item is owned/controlled by me
        }
    }
    else
    {
   			//add by:nestor dispay station name
   			/*
	    if(_aprs_show_new_station_alert)
    {
	    const gchar *msg2 = g_strdup_printf("New station: %s", call);
	    hildon_banner_show_information(_window, NULL, msg2);
    }*/
		//add end

 	//fprintf(stderr, "DEBUG: Station:,New,:%s:\n", call);

        p_station = add_new_station(p_station,p_time,call);     // create storage
        new_station = (char)TRUE;                       // for new station

    }


    if (p_station != NULL)
    {

        last_lat = p_station->coord_lat;                // remember last position
        last_lon = p_station->coord_lon;
        last_stn_sec = p_station->sec_heard;
        snprintf(last_alt,
            sizeof(last_alt),
            "%s",
            p_station->altitude);
        snprintf(last_speed,
            sizeof(last_speed),
            "%s",
            p_station->speed);
        snprintf(last_course,
            sizeof(last_course),
            "%s",
            p_station->course);
        last_flag = p_station->flag;

        // Wipe out old data so that it doesn't hang around forever
        p_station->altitude[0] = '\0';
        p_station->speed[0] = '\0';
        p_station->course[0] = '\0';

        ok = 1;                         // succeed as default


        switch (type) {

            case (APRS_MICE):           // Mic-E format
            case (APRS_FIXED):          // '!'
            case (APRS_MSGCAP):         // '='

                if (!extract_position(p_station,&data,type)) {          // uncompressed lat/lon
                    compr_pos = 1;
                    if (!extract_comp_position(p_station,&data,type))   // compressed lat/lon
                        ok = 0;
                    else
                        p_station->pos_amb = 0; // No ambiguity in compressed posits
                }

                if (ok) {

                    // Create a timestamp from the current time
                    snprintf(p_station->pos_time,
                        sizeof(p_station->pos_time),
                        "%s", get_time(temp_data));
                    (void)extract_storm(p_station,data,compr_pos);
                    (void)extract_weather(p_station,data,compr_pos);    // look for weather data first
                    process_data_extension(p_station,data,type);        // PHG, speed, etc.
                    process_info_field(p_station,data,type);            // altitude

                    if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
                        extract_multipoints(p_station, data, type, 1);

                    add_comment(p_station,data);

                    p_station->record_type = NORMAL_APRS;
                    if (type == APRS_MSGCAP)
                        p_station->flag |= ST_MSGCAP;           // set "message capable" flag
                    else
                        p_station->flag &= (~ST_MSGCAP);        // clear "message capable" flag

                    // Assign a non-default value for the error
                    // ellipse?
                    if (type == APRS_MICE || !compr_pos) {
                        p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
                        p_station->lat_precision = 60;
                        p_station->lon_precision = 60;
                    }
                    else {
                        p_station->error_ellipse_radius = 600; // Default of 6m
                        p_station->lat_precision = 6;
                        p_station->lon_precision = 6;
                    }

                }
                break;

/*
            case (APRS_DOWN):           // '/'
                ok = extract_time(p_station, data, type);               // we need a time
                if (ok) {
                    if (!extract_position(p_station,&data,type)) {      // uncompressed lat/lon
                        compr_pos = 1;
                        if (!extract_comp_position(p_station,&data,type)) // compressed lat/lon
                            ok = 0;
                        else
                            p_station->pos_amb = 0; // No ambiguity in compressed posits
                    }
                }

                if (ok) {

                    // Create a timestamp from the current time
                    snprintf(p_station->pos_time,
                        sizeof(p_station->pos_time),
                        "%s",
                        get_time(temp_data));
                    process_data_extension(p_station,data,type);        // PHG, speed, etc.
                    process_info_field(p_station,data,type);            // altitude

                    if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
                        extract_multipoints(p_station, data, type, 1);

                    add_comment(p_station,data);

                    p_station->record_type = DOWN_APRS;
                    p_station->flag &= (~ST_MSGCAP);            // clear "message capable" flag

                    // Assign a non-default value for the error
                    // ellipse?
                    if (!compr_pos) {
                        p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
                        p_station->lat_precision = 60;
                        p_station->lon_precision = 60;
                    }
                    else {
                        p_station->error_ellipse_radius = 600; // Default of 6m
                        p_station->lat_precision = 6;
                        p_station->lon_precision = 6;
                    }
                }
                break;
*/

            case (APRS_DF):             // '@'
            case (APRS_MOBILE):         // '@'

                ok = extract_time(p_station, data, type);               // we need a time
                if (ok) {
                    if (!extract_position(p_station,&data,type)) {      // uncompressed lat/lon
                        compr_pos = 1;
                        if (!extract_comp_position(p_station,&data,type)) // compressed lat/lon
                            ok = 0;
                        else
                            p_station->pos_amb = 0; // No ambiguity in compressed posits
                    }
                }
                if (ok) {

                    process_data_extension(p_station,data,type);        // PHG, speed, etc.
                    process_info_field(p_station,data,type);            // altitude

                    if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
                        extract_multipoints(p_station, data, type, 1);

                    add_comment(p_station,data);

                    if(type == APRS_MOBILE)
                        p_station->record_type = MOBILE_APRS;
                    else
                        p_station->record_type = DF_APRS;
                    //@ stations have messaging per spec
                    p_station->flag |= (ST_MSGCAP);            // set "message capable" flag

                    // Assign a non-default value for the error
                    // ellipse?
                    if (!compr_pos) {
                        p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
                        p_station->lat_precision = 60;
                        p_station->lon_precision = 60;
                    }
                    else {
                        p_station->error_ellipse_radius = 600; // Default of 6m
                        p_station->lat_precision = 6;
                        p_station->lon_precision = 6;
                    }
                }
                break;

            case (APRS_GRID):

                ok = extract_position(p_station, &data, type);
                if (ok) {


                    process_info_field(p_station,data,type);            // altitude

                    if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
                        extract_multipoints(p_station, data, type, 1);

                    add_comment(p_station,data);

                    // Assign a non-default value for the error
                    // ellipse?
//                    p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution

// WE7U
// This needs to change based on the number of grid letters/digits specified
//                    p_station->lat_precision = 60;
//                    p_station->lon_precision = 60;
                }
                else {
                }
                break;

            case (STATION_CALL_DATA):

                p_station->record_type = NORMAL_APRS;
                found_pos = 0;
                break;

            case (APRS_STATUS):         // '>' Status Reports     [APRS Reference, chapter 16]

                (void)extract_time(p_station, data, type);              // we need a time
                // todo: could contain Maidenhead or beam heading+power

                if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
                    extract_multipoints(p_station, data, type, 1);

                add_status(p_station,data);

                p_station->flag |= (ST_STATUS);                         // set "Status" flag
                p_station->record_type = NORMAL_APRS;                   // ???
                found_pos = 0;
                break;

            case (OTHER_DATA):          // Other Packets          [APRS Reference, chapter 19]

                // non-APRS beacons, treated as status reports until we get a real one

                if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
                    extract_multipoints(p_station, data, type, 1);

                if ((p_station->flag & (~ST_STATUS)) == 0) {            // only store if no status yet

                    add_status(p_station,data);

                    p_station->record_type = NORMAL_APRS;               // ???
                }
                found_pos = 0;
                break;

            case (APRS_OBJECT):

                // If old match is a killed Object (owner doesn't
                // matter), new one is an active Object and owned by
                // us, remove the old record and create a new one
                // for storing this Object.  Do the same for Items
                // in the next section below.
                //
                // The easiest implementation might be to remove the
                // old record and then call this routine again with
                // the same parameters, which will cause a brand-new
                // record to be created.
                //
                // The new record we're processing is an active
                // object, as data_add() won't be called on a killed
                // object.
                //
//                if ( is_my_call(origin,1)  // If new Object is owned by me (including SSID)
                if (new_origin_is_mine
                        && !(p_station->flag & ST_ACTIVE)
                        && (p_station->flag & ST_OBJECT) ) {  // Old record was a killed Object
                    remove_name(p_station);  // Remove old killed Object
//                    redo_list = (int)TRUE;
                    return( data_add(type, call_sign, path, data, port, origin, third_party, 1, 1) );
                }

                ok = extract_time(p_station, data, type);               // we need a time
                if (ok) {
                    if (!extract_position(p_station,&data,type)) {      // uncompressed lat/lon
                        compr_pos = 1;
                        if (!extract_comp_position(p_station,&data,type)) // compressed lat/lon
                            ok = 0;
                        else
                            p_station->pos_amb = 0; // No ambiguity in compressed posits
                    }
                }
                p_station->flag |= ST_OBJECT;                           // Set "Object" flag
                if (ok) {

                    // If object was owned by me but another station
                    // is transmitting it now, write entries into
                    // the object.log file showing that we don't own
                    // this object anymore.

//                    if ( (is_my_call(p_station->origin,1))  // If station was owned by me (including SSID)
//                            && (!is_my_call(origin,1)) ) {  // But isn't now
  // TODO
/*
                	if (is_my_object_item(p_station)    // If station was owned by me (include SSID)
                            && !new_origin_is_mine) {   // But isn't now

                        disown_object_item(call_sign, origin);

                    }
*/
                    // If station is owned by me (including SSID)
                    // but it's a new object/item
//                    if ( (is_my_call(p_station->origin,1))
                    if (new_origin_is_mine
                            && (p_station->transmit_time_increment == 0) ) {
                        // This will get us transmitting this object
                        // on the decaying algorithm schedule.
                        // We've transmitted it once if we've just
                        // gotten to this code.
                        p_station->transmit_time_increment = OBJECT_CHECK_RATE;
//fprintf(stderr,"data_add(): Setting transmit_time_increment to %d\n", OBJECT_CHECK_RATE);
                    }

                    // Create a timestamp from the current time
                    snprintf(p_station->pos_time,
                        sizeof(p_station->pos_time),
                        "%s",
                        get_time(temp_data));

                    snprintf(p_station->origin,
                        sizeof(p_station->origin),
                        "%s",
                        origin);                   // define it as object
                    (void)extract_storm(p_station,data,compr_pos);
                    (void)extract_weather(p_station,data,compr_pos);    // look for wx info
                    process_data_extension(p_station,data,type);        // PHG, speed, etc.
                    process_info_field(p_station,data,type);            // altitude

                    if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
                        extract_multipoints(p_station, data, type, 0);

                    add_comment(p_station,data);

                    // the last char always was missing...
                    //p_station->comments[ strlen(p_station->comments) - 1 ] = '\0';  // Wipe out '\n'
                    // moved that to decode_ax25_line
                    // and don't added a '\n' in interface.c
                    p_station->record_type = NORMAL_APRS;
                    p_station->flag &= (~ST_MSGCAP);            // clear "message capable" flag

                    // Assign a non-default value for the error
                    // ellipse?
                    if (!compr_pos) {
                        p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
                        p_station->lat_precision = 60;
                        p_station->lon_precision = 60;
                    }
                    else {
                        p_station->error_ellipse_radius = 600; // Default of 6m
                        p_station->lat_precision = 6;
                        p_station->lon_precision = 6;
                    }
                }
                break;

            case (APRS_ITEM):

                // If old match is a killed Item (owner doesn't
                // matter), new one is an active Item and owned by
                // us, remove the old record and create a new one
                // for storing this Item.  Do the same for Objects
                // in the previous section above.
                //
                // The easiest implementation might be to remove the
                // old record and then call this routine again with
                // the same parameters, which will cause a brand-new
                // record to be created.
                //
                // The new record we're processing is an active
                // Item, as data_add() won't be called on a killed
                // Item.
                //
//                if ( is_my_call(origin,1)  // If new Item is owned by me (including SSID)
                if (new_origin_is_mine
                        && !(p_station->flag & ST_ACTIVE)
                        && (p_station->flag & ST_ITEM) ) {  // Old record was a killed Item

                    remove_name(p_station);  // Remove old killed Item
//                    redo_list = (int)TRUE;
                    return( data_add(type, call_sign, path, data, port, origin, third_party, 1, 1) );
                }

                if (!extract_position(p_station,&data,type)) {          // uncompressed lat/lon
                    compr_pos = 1;
                    if (!extract_comp_position(p_station,&data,type))   // compressed lat/lon
                        ok = 0;
                    else
                        p_station->pos_amb = 0; // No ambiguity in compressed posits
                }
                p_station->flag |= ST_ITEM;                             // Set "Item" flag
                if (ok) {

                    // If item was owned by me but another station
                    // is transmitting it now, write entries into
                    // the object.log file showing that we don't own
                    // this item anymore.
// TODO
/*
//                    if ( (is_my_call(p_station->origin,1))  // If station was owned by me (including SSID)
//                            && (!is_my_call(origin,1)) ) {  // But isn't now
                    if (is_my_object_item(p_station)
                            && !new_origin_is_mine) {  // But isn't now

                        disown_object_item(call_sign,origin);
                    }
 */

                    // If station is owned by me (including SSID)
                    // but it's a new object/item
//                    if ( (is_my_call(p_station->origin,1))
                    if (is_my_object_item(p_station)
                            && (p_station->transmit_time_increment == 0) ) {
                        // This will get us transmitting this object
                        // on the decaying algorithm schedule.
                        // We've transmitted it once if we've just
                        // gotten to this code.
                        p_station->transmit_time_increment = OBJECT_CHECK_RATE;
//fprintf(stderr,"data_add(): Setting transmit_time_increment to %d\n", OBJECT_CHECK_RATE);
                    }

                    // Create a timestamp from the current time
                    snprintf(p_station->pos_time,
                        sizeof(p_station->pos_time),
                        "%s",
                        get_time(temp_data));
                    snprintf(p_station->origin,
                        sizeof(p_station->origin),
                        "%s",
                        origin);                   // define it as item
                    (void)extract_storm(p_station,data,compr_pos);
                    (void)extract_weather(p_station,data,compr_pos);    // look for wx info
                    process_data_extension(p_station,data,type);        // PHG, speed, etc.
                    process_info_field(p_station,data,type);            // altitude

                    if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
                        extract_multipoints(p_station, data, type, 0);

                    add_comment(p_station,data);

                    // the last char always was missing...
                    //p_station->comments[ strlen(p_station->comments) - 1 ] = '\0';  // Wipe out '\n'
                    // moved that to decode_ax25_line
                    // and don't added a '\n' in interface.c
                    p_station->record_type = NORMAL_APRS;
                    p_station->flag &= (~ST_MSGCAP);            // clear "message capable" flag

                    // Assign a non-default value for the error
                    // ellipse?
                    if (!compr_pos) {
                        p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
                        p_station->lat_precision = 60;
                        p_station->lon_precision = 60;
                    }
                    else {
                        p_station->error_ellipse_radius = 600; // Default of 6m
                        p_station->lat_precision = 6;
                        p_station->lon_precision = 6;
                    }
                }
                break;

            case (APRS_WX1):    // weather in '@' or '/' packet

                ok = extract_time(p_station, data, type);               // we need a time
                if (ok) {
                    if (!extract_position(p_station,&data,type)) {      // uncompressed lat/lon
                        compr_pos = 1;
                        if (!extract_comp_position(p_station,&data,type)) // compressed lat/lon
                            ok = 0;
                        else
                            p_station->pos_amb = 0; // No ambiguity in compressed posits
                    }
                }
                if (ok) {

                    (void)extract_storm(p_station,data,compr_pos);
                    (void)extract_weather(p_station,data,compr_pos);
                    p_station->record_type = (char)APRS_WX1;

                    process_info_field(p_station,data,type);            // altitude

                    if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
                        extract_multipoints(p_station, data, type, 1);

                    add_comment(p_station,data);

                    // Assign a non-default value for the error
                    // ellipse?
                    if (!compr_pos) {
                        p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
                        p_station->lat_precision = 60;
                        p_station->lon_precision = 60;
                    }
                    else {
                        p_station->error_ellipse_radius = 600; // Default of 6m
                        p_station->lat_precision = 6;
                        p_station->lon_precision = 6;
                    }
                }
                break;

            case (APRS_WX2):            // '_'

                ok = extract_time(p_station, data, type);               // we need a time
                if (ok) {
                    (void)extract_storm(p_station,data,compr_pos);
                    (void)extract_weather(p_station,data,0);            // look for weather data first
                    p_station->record_type = (char)APRS_WX2;
                    found_pos = 0;

                    process_info_field(p_station,data,type);            // altitude

                    if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
                        extract_multipoints(p_station, data, type, 1);
                }
                break;

            case (APRS_WX4):            // '#'          Peet Bros U-II (km/h)
            case (APRS_WX6):            // '*'          Peet Bros U-II (mph)
            case (APRS_WX3):            // '!'          Peet Bros Ultimeter 2000 (data logging mode)
            case (APRS_WX5):            // '$ULTW'      Peet Bros Ultimeter 2000 (packet mode)


                if (get_weather_record(p_station)) {    // get existing or create new weather record
                    weather = p_station->weather_data;
                    if (type == APRS_WX3)   // Peet Bros Ultimeter 2000 data logging mode
                    {
                        decode_U2000_L (1,(unsigned char *)data,weather);
                    }
                    else if (type == APRS_WX5) // Peet Bros Ultimeter 2000 packet mode
                        decode_U2000_P(1,(unsigned char *)data,weather);
                    else    // Peet Bros Ultimeter-II
                        decode_Peet_Bros(1,(unsigned char *)data,weather,type);
                    p_station->record_type = (char)type;
                    // Create a timestamp from the current time
                    snprintf(weather->wx_time,
                        sizeof(weather->wx_time),
                        "%s",
                        get_time(temp_data));
                    weather->wx_sec_time = sec_now();
                    found_pos = 0;
                }

                break;


// GPRMC, digits after decimal point
// ---------------------------------
// 2  = 25.5 meter error ellipse
// 3  =  6.0 meter error ellipse
// 4+ =  6.0 meter error ellipse


            case (GPS_RMC):             // $GPRMC

// WE7U
// Change this function to return HDOP and the number of characters
// after the decimal point.
                ok = extract_RMC(p_station,data,call_sign,path,&num_digits);

                if (ok) {
                    // Assign a non-default value for the error
                    // ellipse?
//
// WE7U
// Degrade based on the precision provided in the sentence.  If only
// 2 digits after decimal point, give it 2550 as a radius (25.5m).
// Best (smallest) circle should be 600 as we have no augmentation
// flag to check here for anything better.
//
                    switch (num_digits) {

                        case 0:
                            p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
                            p_station->lat_precision = 6000;
                            p_station->lon_precision = 6000;
                            break;

                        case 1:
                            p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
                            p_station->lat_precision = 600;
                            p_station->lon_precision = 600;
                            break;

                        case 2:
                            p_station->error_ellipse_radius = 600; // Default of 6m
                            p_station->lat_precision = 60;
                            p_station->lon_precision = 60;
                            break;

                        case 3:
                            p_station->error_ellipse_radius = 600; // Default of 6m
                            p_station->lat_precision = 6;
                            p_station->lon_precision = 6;
                            break;

                        case 4:
                        case 5:
                        case 6:
                        case 7:
                            p_station->error_ellipse_radius = 600; // Default of 6m
                            p_station->lat_precision = 0;
                            p_station->lon_precision = 0;
                            break;

                        default:
                            p_station->error_ellipse_radius = 600; // Default of 6m
                            p_station->lat_precision = 60;
                            p_station->lon_precision = 60;
                            break;
                    }
                }
                break;


// GPGGA, digits after decimal point, w/o augmentation
// ---------------------------------------------------
// 2   = 25.5 meter error ellipse
// 3   =  6.0 meter error ellipse unless HDOP>4, then 10.0 meters
// 4+  =  6.0 meter error ellipse unless HDOP>4, then 10.0 meters
//
//
// GPGGA, digits after decimal point, w/augmentation
// --------------------------------------------------
// 2   = 25.5 meter error ellipse
// 3   =  2.5 meter error ellipse unless HDOP>4, then 10.0 meters
// 4+  =  0.6 meter error ellipse unless HDOP>4, then 10.0 meters


            case (GPS_GGA):             // $GPGGA

// WE7U
// Change this function to return HDOP and the number of characters
// after the decimal point.
                ok = extract_GGA(p_station,data,call_sign,path,&num_digits);

                if (ok) {
                    // Assign a non-default value for the error
                    // ellipse?
//
// WE7U
// Degrade based on the precision provided in the sentence.  If only
// 2 digits after decimal point, give it 2550 as a radius (25.5m).
// 3 digits: 6m w/o augmentation unless HDOP >4 = 10m, 2.5m w/augmentation.
// 4+ digits: 6m w/o augmentation unless HDOP >4 = 10m, 0.6m w/augmentation.
//
                     switch (num_digits) {

                        case 0:
                            p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
                            p_station->lat_precision = 6000;
                            p_station->lon_precision = 6000;
                            break;

                        case 1:
                            p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
                            p_station->lat_precision = 600;
                            p_station->lon_precision = 600;
                            break;

                        case 2:
                            p_station->error_ellipse_radius = 600; // Default of 6m
                            p_station->lat_precision = 60;
                            p_station->lon_precision = 60;
                            break;

                        case 3:
                            p_station->error_ellipse_radius = 600; // Default of 6m
                            p_station->lat_precision = 6;
                            p_station->lon_precision = 6;
                            break;

                        case 4:
                        case 5:
                        case 6:
                        case 7:
                            p_station->error_ellipse_radius = 600; // Default of 6m
                            p_station->lat_precision = 0;
                            p_station->lon_precision = 0;
                            break;

                        default:
                            p_station->error_ellipse_radius = 600; // Default of 6m
                            p_station->lat_precision = 60;
                            p_station->lon_precision = 60;
                            break;
                    }
                }
                break;


// GPGLL, digits after decimal point
// ---------------------------------
// 2  = 25.5 meter error ellipse
// 3  =  6.0 meter error ellipse
// 4+ =  6.0 meter error ellipse


            case (GPS_GLL):             // $GPGLL
                ok = extract_GLL(p_station,data,call_sign,path,&num_digits);

                if (ok) {
                    // Assign a non-default value for the error
                    // ellipse?
//
// WE7U
// Degrade based on the precision provided in the sentence.  If only
// 2 digits after decimal point, give it 2550 as a radius, otherwise
// give it 600.
//
                     switch (num_digits) {

                        case 0:
                            p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
                            p_station->lat_precision = 6000;
                            p_station->lon_precision = 6000;
                            break;

                        case 1:
                            p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
                            p_station->lat_precision = 600;
                            p_station->lon_precision = 600;
                            break;

                        case 2:
                            p_station->error_ellipse_radius = 600; // Default of 6m
                            p_station->lat_precision = 60;
                            p_station->lon_precision = 60;
                            break;

                        case 3:
                            p_station->error_ellipse_radius = 600; // Default of 6m
                            p_station->lat_precision = 6;
                            p_station->lon_precision = 6;
                            break;

                        case 4:
                        case 5:
                        case 6:
                        case 7:
                            p_station->error_ellipse_radius = 600; // Default of 6m
                            p_station->lat_precision = 0;
                            p_station->lon_precision = 0;
                            break;

                        default:
                            p_station->error_ellipse_radius = 600; // Default of 6m
                            p_station->lat_precision = 60;
                            p_station->lon_precision = 60;
                            break;
                    }

                }
                break;

            default:

                fprintf(stderr,"ERROR: UNKNOWN TYPE in data_add\n");
                ok = 0;
                break;
        }

        // Left this one in, just in case.  Perhaps somebody might
        // attach a multipoint string onto the end of a packet we
        // might not expect.  For this case we need to check whether
        // we have multipoints first, as we don't want to erase the
        // work we might have done with a previous call to
        // extract_multipoints().
        if (ok && (p_station->coord_lat > 0)
                && (p_station->coord_lon > 0)
                && (p_station->num_multipoints == 0) ) {  // No multipoints found yet

            extract_multipoints(p_station, data, type, 0);
        }
    }

    if (!ok) {  // non-APRS beacon, treat it as Other Packet   [APRS Reference, chapter 19]

        // GPRMC etc. without a position is here too, but it should not be stored as status!

        // store it as status report until we get a real one
        if ((p_station->flag & (~ST_STATUS)) == 0) {         // only store it if no status yet

            add_status(p_station,data-1);

            p_station->record_type = NORMAL_APRS;               // ???

        }

        ok = 1;
        found_pos = 0;
    }

    curr_sec = sec_now();
    if (ok) {

        // data packet is valid
        // announce own echo, we soon discard that packet...
//        if (!new_station && is_my_call(p_station->call_sign,1) // Check SSID as well
        if (!new_station
                && is_my_station(p_station) // Check SSID as well
                && strchr(path,'*') != NULL) {

            upd_echo(path);   // store digi that echoes my signal...
            statusline("Echo from digipeater",0);   // Echo from digipeater

        }
        // check if data is just a secondary echo from another digi
        if ((last_flag & ST_VIATNC) == 0
                || (curr_sec - last_stn_sec) > 15
                || p_station->coord_lon != last_lon
                || p_station->coord_lat != last_lat)

            store = 1;                     // don't store secondary echos
    }

    if (!ok && new_station)
        delete_station_memory(p_station);       // remove unused record

    if (store) {

        // we now have valid data to store into database
        // make time index unique by adding a serial number

        if (station_is_mine) {
            // This station/object/item is owned by me.  Set the
            // flag which shows that we own/control this
            // station/object/item.  We use this flag later in lieu
            // of the is_my_call() function in order to speed things
            // up.
            //
            p_station->flag |= ST_MYSTATION;
        }

        // Check whether it's a locally-owned object/item
        if ( object_is_mine
                || (   new_origin_is_mine
                    && (p_station->flag & ST_ACTIVE)
                    && (p_station->flag & ST_OBJECT) ) ) {

            p_station->flag |= ST_MYOBJITEM;

            // Do nothing else.  We don't want to update the
            // last-heard time so that it'll expire from the queue
            // normally, unless it is a new object/item.
            //
            if (new_station) {
                p_station->sec_heard = curr_sec;
            }

            // We need an exception later in this function for the
            // case where we've moved an object/item (by how much?).
            // We need to update the time in this case so that it'll
            // expire later (in fact it could already be expired
            // when we move it).  We should be able to move expired
            // objects/items to make them active again.  Perhaps
            // some other method as well?.
        }
        else {
            // Reset the "my object" flag
            p_station->flag &= ~ST_MYOBJITEM;

            p_station->sec_heard = curr_sec;    // Give it a new timestamp
        }

        if (curr_sec != last_sec) {     // todo: if old time calculate time_sn from database
            last_sec = curr_sec;
            next_time_sn = 0;           // restart time serial number
        }

        p_station->time_sn = next_time_sn++;            // todo: warning if serial number too high
        if (port == APRS_PORT_TTY) {                     // heard via TNC
            if (!third_party) { // Not a third-party packet
                p_station->flag |= ST_VIATNC;               // set "via TNC" flag
                p_station->heard_via_tnc_last_time = curr_sec;
                p_station->heard_via_tnc_port = port;
            }
            else {  // Third-party packet
                // Leave the previous setting of "flag" alone.
                // Specifically do NOT set the ST_VIATNC flag if it
                // was a third-party packet.
            }
        }
        else {  // heard other than TNC
            if (new_station) {  // new station
                p_station->flag &= (~ST_VIATNC);  // clear "via TNC" flag
//fprintf(stderr,"New_station: Cleared ST_VIATNC flag: %s\n", p_station->call_sign);
                p_station->heard_via_tnc_last_time = 0l;
            }
        }
        p_station->last_port_heard = port;

        // Create a timestamp from the current time
        snprintf(p_station->packet_time, sizeof(p_station->packet_time),
            "%s", get_time(temp_data)); // get_time returns value in temp_data

        p_station->flag |= ST_ACTIVE;
        if (third_party)
            p_station->flag |= ST_3RD_PT;               // set "third party" flag
        else
            p_station->flag &= (~ST_3RD_PT);            // clear "third party" flag
        if (origin != NULL && strcmp(origin,"INET") == 0)  // special treatment for inet names
            snprintf(p_station->origin,
                sizeof(p_station->origin),
                "%s",
                origin);           // to keep them separated from calls
        if (origin != NULL && strcmp(origin,"INET-NWS") == 0)  // special treatment for NWS
            snprintf(p_station->origin,
                sizeof(p_station->origin),
                "%s",
                origin);           // to keep them separated from calls
        if (origin == NULL || origin[0] == '\0')        // normal call
            p_station->origin[0] = '\0';                // undefine possible former object with same name


        //--------------------------------------------------------------------

        // KC2ELS
        // Okay, here are the standards for ST_DIRECT:
        // 1.  The packet must have been received via TNC.
        // 2.  The packet must not have any * flags.
        // 3.  If present, the first WIDEn-N (or TRACEn-N) must have n=N.
        // A station retains the ST_DIRECT setting.  If
        // "st_direct_timeout" seconds have passed since we set
        // that bit then APRSD queries and displays based on the
        // ST_DIRECT bit will skip that station.

// In order to make this scheme work for stations that straddle both
// RF and INET, we need to make sure that node_path_ptr doesn't get
// overwritten with an INET path if there's an RF path already in
// there and it has been less than st_direct_timeout seconds since
// the station was last heard on RF.

        if ((port == APRS_PORT_TTY)             // Heard via TNC
                && !third_party                // Not a 3RD-Party packet
                && path != NULL                // Path is not NULL
                && strchr(path,'*') == NULL) { // No asterisk found

            // Look for WIDE or TRACE
            if ((((p = strstr(path,"WIDE")) != NULL)
                    && (p+=4)) ||
                    (((p = strstr(path,"TRACE")) != NULL)
                    && (p+=5))) {

                // Look for n=N on WIDEn-N/TRACEn-N digi field
                if ((*p != '\0') && isdigit((int)*p)) {
                    if ((*(p+1) != '\0') && (*(p+1) == '-')) {
                        if ((*(p+2) != '\0') && g_ascii_isdigit(*(p+2))) {
                            if (*(p) == *(p+2)) {
                                direct = 1;
                            }
                            else {
                                direct = 0;
                            }
                        }
                        else {
                            direct = 0;
                        }
                    }
                    else {
                        direct = 0;
                    }
                }
                else {
                    direct = 1;
                }
            }
            else {
                direct = 1;
            }
        }
        else {
            direct = 0;
        }


        if (direct == 1) {
            // This packet was heard direct.  Set the ST_DIRECT bit
            // and save the timestamp away.
            p_station->direct_heard = curr_sec;
            p_station->flag |= (ST_DIRECT);
        }
        else {
            // This packet was NOT heard direct.  Check whether we
            // need to expire the ST_DIRECT bit.  A lot of fixed
            // stations transmit every 30 minutes.  One hour gives
            // us time to receive a direct packet from them among
            // all the digipeated packets.

            if ((p_station->flag & ST_DIRECT) != 0 &&
                    curr_sec > (p_station->direct_heard + st_direct_timeout)) {
                p_station->flag &= (~ST_DIRECT);
            }
        }

        // If heard on TNC, overwrite node_path_ptr if any of these
        // conditions are met:
        //     *) direct == 1 (packet was heard direct)
        //     *) ST_DIRECT flag == 0 (packet hasn't been heard
        //     direct recently)
        //     *) ST_DIRECT is set, st_direct_timeout has expired
        //     (packet hasn't been heard direct recently)
        //
        // These rules will allow us to keep directly heard paths
        // saved for at least an hour (st_direct_timeout), and not
        // get overwritten with digipeated paths during that time.
        //
        if ((port == APRS_PORT_TTY)  // Heard via TNC
                && !third_party     // Not a 3RD-Party packet
                && path != NULL) {  // Path is not NULL

            // Heard on TNC interface and not third party.  Check
            // the other conditions listed in the comments above to
            // decide whether we should overwrite the node_path_ptr
            // variable.
            //
            if ( direct   // This packet was heard direct
                 || (p_station->flag & ST_DIRECT) == 0  // Not heard direct lately
                 || ( (p_station->flag & ST_DIRECT) != 0 // Not heard direct lately
                      && (curr_sec > (p_station->direct_heard+st_direct_timeout) ) ) ) {

                // Free any old path we might have
                if (p_station->node_path_ptr != NULL)
                    free(p_station->node_path_ptr);
                // Malloc and store the new path
                p_station->node_path_ptr = (char *)malloc(strlen(path) + 1);
                CHECKMALLOC(p_station->node_path_ptr);

                substr(p_station->node_path_ptr,path,strlen(path));
            }
        }
        // If a 3rd-party packet heard on TNC, overwrite
        // node_path_ptr only if heard_via_tnc_last_time is older
        // than one hour (zero counts as well!), plus clear the
        // ST_DIRECT and ST_VIATNC bits in this case.  This makes us
        // keep the RF path around for at least one hour after the
        // station is heard.
        //
        else if ((port == APRS_PORT_TTY)  // Heard via TNC
                && third_party      // It's a 3RD-Party packet
                && path != NULL) {  // Path is not NULL

            // 3rd-party packet heard on TNC interface.  Check if
            // heard_via_tnc_last_time is older than an hour.  If
            // so, overwrite the path and clear a few bits to show
            // that it has timed out on RF and we're now receiving
            // that station from an igate.
            //
            if (curr_sec > (p_station->heard_via_tnc_last_time + 60*60)) {

                // Yep, more than one hour old or is a zero,
                // overwrite the node_path_ptr variable with the new
                // one.  We're only hearing this station on INET
                // now.

                // Free any old path we might have
                if (p_station->node_path_ptr != NULL)
                    free(p_station->node_path_ptr);
                // Malloc and store the new path
                p_station->node_path_ptr = (char *)malloc(strlen(path) + 1);
                CHECKMALLOC(p_station->node_path_ptr);

                substr(p_station->node_path_ptr,path,strlen(path));

                // Clear the ST_VIATNC bit
                p_station->flag &= ~ST_VIATNC;
            }

            // If direct_heard is over an hour old, clear the
            // ST_DIRECT flag.  We're only hearing this station on
            // INET now.
            //
            if (curr_sec > (p_station->direct_heard + st_direct_timeout)) {

                // Yep, more than one hour old or is a zero, clear
                // the ST_DIRECT flag.
                p_station->flag &= ~ST_DIRECT;
            }
        }

        // If heard on INET then overwrite node_path_ptr only if
        // heard_via_tnc_last_time is older than one hour (zero
        // counts as well!), plus clear the ST_DIRECT and ST_VIATNC
        // bits in this case.  This makes us keep the RF path around
        // for at least one hour after the station is heard.
        //
        else if (port != APRS_PORT_TTY  // From an INET interface
                && !third_party        // Not a 3RD-Party packet
                && path != NULL) {     // Path is not NULL

            // Heard on INET interface.  Check if
            // heard_via_tnc_last_time is older than an hour.  If
            // so, overwrite the path and clear a few bits to show
            // that it has timed out on RF and we're now receiving
            // that station from the INET feeds.
            //
            if (curr_sec > (p_station->heard_via_tnc_last_time + 60*60)) {

                // Yep, more than one hour old or is a zero,
                // overwrite the node_path_ptr variable with the new
                // one.  We're only hearing this station on INET
                // now.

                // Free any old path we might have
                if (p_station->node_path_ptr != NULL)
                    free(p_station->node_path_ptr);
                // Malloc and store the new path
                p_station->node_path_ptr = (char *)malloc(strlen(path) + 1);
                CHECKMALLOC(p_station->node_path_ptr);

                substr(p_station->node_path_ptr,path,strlen(path));

                // Clear the ST_VIATNC bit
                p_station->flag &= ~ST_VIATNC;

            }

            // If direct_heard is over an hour old, clear the
            // ST_DIRECT flag.  We're only hearing this station on
            // INET now.
            //
            if (curr_sec > (p_station->direct_heard + st_direct_timeout)) {

                // Yep, more than one hour old or is a zero, clear
                // the ST_DIRECT flag.
                p_station->flag &= ~ST_DIRECT;
            }
        }


        //---------------------------------------------------------------------

        p_station->num_packets += 1;


        screen_update = 0;
        if (new_station) {
            if (strlen(p_station->speed) > 0 && atof(p_station->speed) > 0) {
                p_station->flag |= (ST_MOVING); // it has a speed, so it's moving
                moving = 1;
            }

            if (p_station->coord_lat != 0 && p_station->coord_lon != 0) {   // discard undef positions from screen
                if (!altnet || is_altnet(p_station) ) {

                    screen_update = 1;
                    // TODO - check needed
                }

            }
        }
        else {        // we had seen this station before...

        	if (found_pos && position_defined(p_station->coord_lat,p_station->coord_lon,1)) { // ignore undefined and 0N/0E
                if (p_station->newest_trackpoint != NULL) {
                    moving = 1;                         // it's moving if it has a trail
                }
                else {
                    if (strlen(p_station->speed) > 0 && atof(p_station->speed) > 0) {
                        moving = 1;                     // declare it moving, if it has a speed
                    }
                    else {
                        // Here's where we detect movement
                        if (position_defined(last_lat,last_lon,1)
                                && (p_station->coord_lat != last_lat || p_station->coord_lon != last_lon)) {
                            moving = 1;                 // it's moving if it has changed the position
                        }
                        else {
                            moving = 0;
                        }
                    }
                }
                changed_pos = 0;
                if (moving == 1) {
                    p_station->flag |= (ST_MOVING);
                    // we have a moving station, process trails
                    if (atoi(p_station->speed) < TRAIL_MAX_SPEED) {     // reject high speed data (undef gives 0)
                        // we now may already have the 2nd position, so store the old one first
                        if (p_station->newest_trackpoint == NULL) {
                            if (position_defined(last_lat,last_lon,1)) {  // ignore undefined and 0N/0E
                                (void)store_trail_point(p_station,
                                                        last_lon,
                                                        last_lat,
                                                        last_stn_sec,
                                                        last_alt,
                                                        last_speed,
                                                        last_course,
                                                        last_flag);
                            }
                        }
                        //if (   p_station->coord_lon != last_lon
                        //    || p_station->coord_lat != last_lat ) {
                        // we don't store redundant points (may change this
                                                // later ?)
                                                //
                        // There are often echoes delayed 15 minutes
                        // or so it looks ugly on the trail, so I
                        // want to discard them This also discards
                        // immediate echoes. Duplicates back in time
                        // up to TRAIL_ECHO_TIME minutes are
                        // discarded.
                        //
                        if (!is_trailpoint_echo(p_station)) {
                            (void)store_trail_point(p_station,
                                                    p_station->coord_lon,
                                                    p_station->coord_lat,
                                                    p_station->sec_heard,
                                                    p_station->altitude,
                                                    p_station->speed,
                                                    p_station->course,
                                                    p_station->flag);
                            changed_pos = 1;

                            // Check whether it's a locally-owned object/item
                            if (object_is_mine) {

                                // Update time, change position in
                                // time-sorted list to change
                                // expiration time.
                                move_station_time(p_station,p_time);

                                // Give it a new timestamp
                                p_station->sec_heard = curr_sec;
                                //fprintf(stderr,"Updating last heard time\n");
                            }
                        }
                    }

//                    if (track_station_on == 1)          // maybe we are tracking a station
//                        track_station(da,tracking_station_call,p_station);
                } // moving...

                // now do the drawing to the screen
                ok_to_display = !altnet || is_altnet(p_station); // Optimization step, needed twice below.
// TODO
//                screen_update = 0;
//                if (changed_pos == 1 && Display_.trail && ((p_station->flag & ST_INVIEW) != 0)) {
//                    if (ok_to_display) {
//                        draw_trail(da,p_station,1);         // update trail
//                        screen_update = 1;
//                    }

                }

                if (changed_pos == 1 || !position_defined(last_lat,last_lon,0)) {
                    if (ok_to_display) {

                        screen_update = 1; // TODO -check needed
                    }

                }
            } // defined position
        }

        if (screen_update) {
            if (p_station->last_port_heard == APRS_PORT_TTY) {   // Data from local TNC

                redraw_on_new_data = 2; // Update all symbols NOW!
            }
          else {
                redraw_on_new_data = 0; // Update each 60 secs
          }
     }

	if(port == APRS_PORT_TTY)
		fprintf(stderr,"(TNC Recv %ld)%s %s %s %s\n",tncr_packet_count++,call_sign, path, pos_info, data);

	if(port == APRS_PORT_INET)
		fprintf(stderr,"(INET Recv %ld)%s %s %s %s\n",inetr_packet_count++,call_sign, path, pos_info, data);

   	gint aprs_igate_method = _aprs_igate_method;

    if(ok)
    {
		//找出那个纠偏参数位置 add by nestor 20100117
		//fprintf(stderr,"收到一个台站:%s\n",call);
//		if(*call=='B' && !strchr('MNOPQUVWX',*(call+1)))	//只有B电台，但不包括台湾地区的电台才需要纠偏

		if(*(pos_info + 7) != 'N' || *(pos_info + 17) != 'E')
		{
			aprs_igate_method=0;	//不含经纬度的数据包，不要再转发了
		}
		if(*call=='B' && !strchr("MNOPQUVWX",*(call+1)))	//只有B电台，但不包括台湾地区的电台才需要纠偏
		{
			double lat,lon,offsetlat,offsetlon;
			p_station->offsetlat=0;
			p_station->offsetlon=0;
			if(*data!='*')
			{
				lat = convert_lat_l2d(p_station->coord_lat) ;
				lon = convert_lon_l2d(p_station->coord_lon) ;
				cal_offset_correct(lat,lon,&offsetlat,&offsetlon);
				p_station->offsetlat=offsetlat;
				p_station->offsetlon=offsetlon;


				//20110213 add by nestor
				//如果offset=2,且处于向互联网发送模式时，把收到台站位置字符串做纠偏处理
				//准备给下面的iGate程序使用，这样发送到互联网的台站就不偏了

				if(_aprs_offset_enable && port == APRS_PORT_TTY && (aprs_igate_method == 1 || aprs_igate_method==3 || aprs_igate_method==5))
				{
					//gps_lat = _gps.lat - _offset_lat;
					//gps_lon = _gps.lon - _offset_lon ;
					//pos_info此时的格式为：3851.30N\12131.51E
					//我们把它替换了
					gchar slat[10];
					gchar slon[10];
					//gdouble pos = (gps_lat > 0 ? gps_lat : 0-gps_lat) ;
					gdouble pos = ((lat + offsetlat) > 0 ? (lat + offsetlat) : 0 - (lat + offsetlat) ) ;
					gdouble min = (pos - (int)pos)*60.0;
					sprintf(slat, "%02d%02d.%02.0f", (int)pos, (int)min,((min - (int)min)*100.0) );

					pos = ((lon + offsetlon) > 0 ? (lon + offsetlon) : 0 - (lon + offsetlon) ) ;

					min = (pos - (int)pos)*60.0;
					sprintf(slon, "%03d%02d.%02.0f", (int)pos, (int)min,((min - (int)min)*100.0) );
					//fprintf(stderr,"\npos_info1(%i):%s slat(%i):%s slon(%i):%s\n",strlen(pos_info),pos_info,strlen(slat),slat,strlen(slon),slon);
					memcpy(pos_info,slat,7);
					memcpy(pos_info+9,slon,8);
					//fprintf(stderr,"\npos_info2(%i):%s slat(%i):%s slon(%i):%s\n",strlen(pos_info),pos_info,strlen(slat),slat,strlen(slon),slon);

				}
				//2011023 add end



			}
			//fprintf(stderr,"%s台站偏移:%lf %lf\n",call,p_station->offsetlat,p_station->offsetlon);
		}

        /*plot_aprs_station( p_station, TRUE );*/
 	//add by:nestor dispay station name
	 if(_aprs_show_new_station_alert)
	 {
	   	gchar course_deg[8];
   		gchar * formatted_distance = NULL;
   		gdouble distance = 0;

   		course_deg[0] = '\0';


   		if(p_station->coord_lat != 0 && p_station->coord_lon != 0)
   		{
   			distance = distance_from_my_station(call, course_deg, sizeof(course_deg));

        		if(_units == UNITS_KM)
        			formatted_distance = g_strdup_printf("%.02f km", distance);
        		else if(_units == UNITS_MI)
        			formatted_distance = g_strdup_printf("%.02f miles", distance);
        		else if(_units == UNITS_NM)
        			formatted_distance = g_strdup_printf("%.02f nm", distance);
  		}
   		else
   		{
   			formatted_distance = g_strdup_printf(" ");
   		}

		/* modify:nestor 更换为大字体版*/
   		gdouble lat,lon;
   		gchar * comment = NULL;
   		lat = convert_lat_l2d(p_station->coord_lat) ;
   		lon = convert_lon_l2d(p_station->coord_lon) ;
   		//fprintf(stderr,"Station:%s",p_station->call_sign);
   		//p_station->aprs_symbol.aprs_symbol, p_station->aprs_symbol.aprs_type

   		if(p_station->comment_data)
   			comment = g_strdup_printf("%c%c%s",p_station->aprs_symbol.aprs_type,p_station->aprs_symbol.aprs_symbol,p_station->comment_data->text_ptr);
   		else
   			comment = g_strdup_printf("%c%c",p_station->aprs_symbol.aprs_type,p_station->aprs_symbol.aprs_symbol);

		if (new_station)
		{
	 		const gchar *msg = g_strdup_printf("*%s  %s  %s", call,formatted_distance,course_deg);
	 		MACRO_BANNER_SHOW_INFO(_window, _(msg));
	 		p_station->poi_id = poi_add_aprs(p_station->poi_id,lat + p_station->offsetlat,lon + p_station->offsetlon,p_station->call_sign,comment,TRUE);

		}
		else
		{
	 		const gchar *msg = g_strdup_printf("%s  %s  %s", call,formatted_distance,course_deg);
	 		MACRO_BANNER_SHOW_INFO(_window, _(msg));
	 		poi_add_aprs(p_station->poi_id,lat + p_station->offsetlat,lon + p_station->offsetlon,p_station->call_sign,comment,FALSE);
		}

		g_free(comment);
		//20110208 刷新列表 add by:nestor
		if(_list_enabled==1)
		 {
			//list_stations();
		 }


	    		//gdouble d_lat = convert_lat_l2d(p_station->coord_lat);
	    		//gdouble d_lon = convert_lon_l2d(p_station->coord_lon);
	    		//Point unit;



		//fprintf(stderr,"beacon:%s\n",_aprs_beacon_comment);
		g_free(formatted_distance);
		//test
		//printf(stderr,_curr_repo->url);
		//

	 }

    }
	//看看是否工作与TNC和iGate模式，然后转发
	//add by:nestor 20110125

	/*data_add参数都在这里看能否好用
	int data_add(gint type,
             gchar *call_sign,
             gchar *path,
             gchar *data,
             TAprsPort port,
             gchar *origin,
             gint third_party,
             gint station_is_mine,
             gint object_is_mine) {
				 */



	//int is_offset,is_gpsout,is_igate;

	//sscanf(_aprs_beacon_comment,"offset=%i;gpsout=%i;igate=%i;",&is_offset,&is_gpsout,&is_igate);
	//fprintf(stderr,"path:%s\n",path);
	//只有没我处理过的路径才转发
	//char *ptr_mycall=NULL;
	//if(path!=NULL) 移到底层函数send_packet
	//	ptr_mycall=strstr(path,_aprs_mycall);
	//0 -- 关闭
	//1 -- TNC -> INET
	//2 -- INET -> TNC
	//3 -- INET <-> TNC
	//4 -- TNC <-> TNC (这个不需要互联网连接)
	//5 -- TNC <-> TNC + INET
	//6 -- TNC 工作在Packet模式下
 	if(aprs_igate_method>0 && aprs_igate_method<6 && _aprs_tty_enable & (_aprs_inet_enable || aprs_igate_method==4))
	{
		//fprintf(stderr,"data2:%s\n",pos_info);
		//fprintf(stderr,"igate !\n");
		if(aprs_igate_method == 1 || aprs_igate_method==3 || aprs_igate_method==4 ||  aprs_igate_method==5)
		{
			//启动向电台转发
			if (port == APRS_PORT_TTY)		//从电台收到数据转发至互联网
			{
				//启动向互联网转发
				//fprintf(stderr,"data2:%s\n",pos_info);
				if(aprs_igate_method==4 || aprs_igate_method==5)
				{
					gchar *packet_tty;
					packet_tty = g_strdup_printf("=%s",pos_info);
					//fprintf(stderr,"igate from radio->radio!\n");
					output_igate_aprs_data(APRS_PORT_TTY,call_sign, path, packet_tty, strlen(packet_tty));
				}
				if(aprs_igate_method==1 || aprs_igate_method==3 || aprs_igate_method==5)
				{
					gchar *packet_inet;
					packet_inet = g_strdup_printf("=%s",pos_info);
					//if (is_igate==5)
					//	usleep(100);
					//fprintf(stderr,"igate from radio->inet!\n");
					output_igate_aprs_data(APRS_PORT_INET,call_sign, path, packet_inet, strlen(packet_inet));
				}
			}

		}
		if(aprs_igate_method == 2 || aprs_igate_method==3 )
		{
			//启动向电台转发
			if (port == APRS_PORT_INET)		//从电台收到数据转发至互联网
			{
				//fprintf(stderr,"igate from inet->radio!\n");
				//启动向电台转发
				gchar *packet_inet2;
				packet_inet2 = g_strdup_printf("=%s",pos_info);
				//fprintf(stderr,"data2:%s\n",pos_info);
				output_igate_aprs_data(APRS_PORT_TTY,call_sign, path, packet_inet2, strlen(packet_inet2));
			}

		}

	}
	//转发结束

    return(ok);
}   // End of data_add() function


int decode_message(gchar *call,gchar *path,gchar *message,gint port,gint third_party);

int decode_Mic_E(char *call_sign,char *path,char *info,int port,int third_party) {
    int  ii;
    int  offset;
    unsigned char s_b1;
    unsigned char s_b2;
    unsigned char s_b3;
    unsigned char s_b4;
    unsigned char s_b5;
    unsigned char s_b6;
    unsigned char s_b7;
    int  north,west,long_offset;
    int  d,m,h;
    char temp[MAX_LINE_SIZE+1];     // Note: Must be big in case we get long concatenated packets
    char new_info[MAX_LINE_SIZE+1]; // Note: Must be big in case we get long concatenated packets
    int  course;
    int  speed;
    int  msg1,msg2,msg3,msg;
    int  info_size;
    long alt;
    int  msgtyp;
    char rig_type[10];
    int ok;

  //	fprintf(stderr, "Enter decode_Mic_E\n");
  // MIC-E Data Format   [APRS Reference, chapter 10]

    // todo:  error check
    //        drop wrong positions from receive errors...
    //        drop 0N/0E position (p.25)

    /* First 7 bytes of info[] contains the APRS data type ID,    */
    /* longitude, speed, course.                    */
    /* The 6-byte destination field of path[] contains latitude,    */
    /* N/S bit, E/W bit, longitude offset, message code.        */
    /*

    MIC-E Destination Field Format:
    -------------------------------
    Ar1DDDD0 Br1DDDD0 Cr1MMMM0 Nr1MMMM0 Lr1HHHH0 Wr1HHHH0 CrrSSID0
    D = Latitude Degrees.
    M = Latitude Minutes.
    H = Latitude Hundredths of Minutes.
    ABC = Message bits, complemented.
    N = N/S latitude bit (N=1).
    W = E/W longitude bit (W=1).
    L = 100's of longitude degrees (L=1 means add 100 degrees to longitude
    in the Info field).
    C = Command/Response flag (see AX.25 specification).
    r = reserved for future use (currently 0).

    */
    /****************************************************************************
    * I still don't handle:                                                     *
    *    Custom message bits                                                    *
    *    SSID special routing                                                   *
    *    Beta versions of the MIC-E (which use a slightly different format).    *
    *                                                                           *
    * DK7IN : lat/long with custom msg works, altitude/course/speed works       *
    *****************************************************************************/


    // Note that the first MIC-E character was not passed to us, so we're
    // starting just past it.
    // Check for valid symbol table character.  Should be '/' or '\'
    // or 0-9, A-Z.
    //
    if (        info[7] == '/'                          // Primary table
            ||  info[7] == '\\'                         // Alternate table
            || (info[7] >= '0' && info[7] <= '9')       // Overlay char
            || (info[7] >= 'A' && info[7] <= 'Z') ) {   // Overlay char

        // We're good, keep going

    }
    else { // Symbol table or overlay char incorrect

        if (info[6] == '/' || info[6] == '\\') {    // Found it back one char in string
            // Don't print out the full info string here because it
            // can contain unprintable characters.  In fact, we
            // should check the chars we do print out to make sure
            // they're printable, else print a space char.
        }

        return(1);  // No good, not MIC-E format or corrupted packet.  Return 1
                    // so that it won't get added to the database at all.
    }

    // Check for valid symbol.  Should be between '!' and '~' only.
    if (info[6] < '!' || info[6] > '~') {

        return(1);  // No good, not MIC-E format or corrupted packet.  Return 1
                    // so that it won't get added to the database at all.
    }

    // Check for minimum MIC-E size.
    if (strlen(info) < 8) {

        return(1);  // No good, not MIC-E format or corrupted packet.  Return 1
                    // so that it won't get added to the database at all.
    }

    // Check for 8-bit characters in the first eight slots.  Not
    // allowed per Mic-E chapter of the spec.
    for (ii = 0; ii < 8; ii++) {
        if ((unsigned char)info[ii] > 0x7f) {
            // 8-bit data was found in the lat/long/course/speed
            // portion.  Bad packet.  Drop it.
//fprintf(stderr, "%s: 8-bits found in Mic-E packet initial portion. Dropping it.\n", call_sign);
            return(1);
        }
    }

    // Check whether we have more data.  If flag character is 0x1d
    // (8-bit telemetry flag) then don't do the 8-bit check below.
    if (strlen(info) > 8) {

        // Check for the 8-bit telemetry flag
        if ((unsigned char)info[8] == 0x1d) {
            // 8-bit telemetry found, skip the check loop below
        }
        else {  // 8-bit telemetry flag was not found.  Check that
                // we only have 7-bit characters through the rest of
                // the packet.

            for (ii = 8; ii < (int)strlen(info); ii++) {

                if ((unsigned char)info[ii] > 0x7f) {
                    // 8-bit data was found.  Bad packet.  Drop it.
//fprintf(stderr, "%s: 8-bits found in Mic-E packet final portion (not 8-bit telemetry). Dropping it.\n", call_sign);
                    return(1);
                }
            }
        }
    }

    //fprintf(stderr,"Path1:%s\n",path);

    msg1 = (int)( ((unsigned char)path[0] & 0x40) >>4 );
    msg2 = (int)( ((unsigned char)path[1] & 0x40) >>5 );
    msg3 = (int)( ((unsigned char)path[2] & 0x40) >>6 );
    msg = msg1 | msg2 | msg3;   // We now have the complemented message number in one variable
    msg = msg ^ 0x07;           // And this is now the normal message number
    msgtyp = 0;                 // DK7IN: Std message, I have to add custom msg decoding

    //fprintf(stderr,"Msg: %d\n",msg);

    /* Snag the latitude from the destination field, Assume TAPR-2 */
    /* DK7IN: latitude now works with custom message */
    s_b1 = (unsigned char)( (path[0] & 0x0f) + (char)0x2f );
    //fprintf(stderr,"path0:%c\ts_b1:%c\n",path[0],s_b1);
    if (path[0] & 0x10)     // A-J
        s_b1 += (unsigned char)1;

    if (s_b1 > (unsigned char)0x39)        // K,L,Z
        s_b1 = (unsigned char)0x20;
    //fprintf(stderr,"s_b1:%c\n",s_b1);

    s_b2 = (unsigned char)( (path[1] & 0x0f) + (char)0x2f );
    //fprintf(stderr,"path1:%c\ts_b2:%c\n",path[1],s_b2);
    if (path[1] & 0x10)     // A-J
        s_b2 += (unsigned char)1;

    if (s_b2 > (unsigned char)0x39)        // K,L,Z
        s_b2 = (unsigned char)0x20;
    //fprintf(stderr,"s_b2:%c\n",s_b2);

    s_b3 = (unsigned char)( (path[2] & (char)0x0f) + (char)0x2f );
    if (path[2] & 0x10)     // A-J
        s_b3 += (unsigned char)1;

    if (s_b3 > (unsigned char)0x39)        // K,L,Z
        s_b3 = (unsigned char)0x20;

    s_b4 = (unsigned char)( (path[3] & 0x0f) + (char)0x30 );
    if (s_b4 > (unsigned char)0x39)        // L,Z
        s_b4 = (unsigned char)0x20;


    s_b5 = (unsigned char)( (path[4] & 0x0f) + (char)0x30 );
    if (s_b5 > (unsigned char)0x39)        // L,Z
        s_b5 = (unsigned char)0x20;


    s_b6 = (unsigned char)( (path[5] & 0x0f) + (char)0x30 );
    //fprintf(stderr,"path5:%c\ts_b6:%c\n",path[5],s_b6);
    if (s_b6 > (unsigned char)0x39)        // L,Z
        s_b6 = (unsigned char)0x20;
    //fprintf(stderr,"s_b6:%c\n",s_b6);

    s_b7 =  (unsigned char)path[6];        // SSID, not used here
    //fprintf(stderr,"path6:%c\ts_b7:%c\n",path[6],s_b7);

    //fprintf(stderr,"\n");

    // Special tests for 'L' due to position ambiguity deviances in
    // the APRS spec table.  'L' has the 0x40 bit set, but they
    // chose in the spec to have that represent position ambiguity
    // _without_ the North/West/Long Offset bit being set.  Yuk!
    // Please also note that the tapr.org Mic-E document (not the
    // APRS spec) has the state of the bit wrong in columns 2 and 3
    // of their table.  Reverse them.
    if (path[3] == 'L')
        north = 0;
    else
        north = (int)((path[3] & 0x40) == (char)0x40);  // N/S Lat Indicator

    if (path[4] == 'L')
        long_offset = 0;
    else
        long_offset = (int)((path[4] & 0x40) == (char)0x40);  // Longitude Offset

    if (path[5] == 'L')
        west = 0;
    else
        west = (int)((path[5] & 0x40) == (char)0x40);  // W/E Long Indicator

    //fprintf(stderr,"north:%c->%d\tlat:%c->%d\twest:%c->%d\n",path[3],north,path[4],long_offset,path[5],west);

    /* Put the latitude string into the temp variable */
    snprintf(temp, sizeof(temp), "%c%c%c%c.%c%c%c%c",s_b1,s_b2,s_b3,s_b4,s_b5,s_b6,
            (north ? 'N': 'S'), info[7]);   // info[7] = symbol table

    /* Compute degrees longitude */
    snprintf(new_info, sizeof(new_info), "%s", temp);
    d = (int) info[0]-28;

    if (long_offset)
        d += 100;

    if ((180<=d)&&(d<=189))  // ??
        d -= 80;

    if ((190<=d)&&(d<=199))  // ??
        d -= 190;

    /* Compute minutes longitude */
    m = (int) info[1]-28;
    if (m>=60)
        m -= 60;

    /* Compute hundredths of minutes longitude */
    h = (int) info[2]-28;
    /* Add the longitude string into the temp variable */
    snprintf(temp, sizeof(temp), "%03d%02d.%02d%c%c",d,m,h,(west ? 'W': 'E'), info[6]);
    strncat(new_info,
        temp,
        sizeof(new_info) - strlen(new_info));

    /* Compute speed in knots */
    speed = (int)( ( info[3] - (char)28 ) * (char)10 );
    speed += ( (int)( (info[4] - (char)28) / (char)10) );
    if (speed >= 800)
        speed -= 800;       // in knots

    /* Compute course */
    course = (int)( ( ( (info[4] - (char)28) % 10) * (char)100) + (info[5] - (char)28) );
    if (course >= 400)
        course -= 400;

    /*  ???
        fprintf(stderr,"info[4]-28 mod 10 - 4 = %d\n",( ( (int)info[4]) - 28) % 10 - 4);
        fprintf(stderr,"info[5]-28 = %d\n", ( (int)info[5]) - 28 );
    */
    snprintf(temp, sizeof(temp), "%03d/%03d",course,speed);
    strncat(new_info,
        temp,
        sizeof(new_info) - strlen(new_info));
    offset = 8;   // start of rest of info

    /* search for rig type in Mic-E data */
	//下文是关于设备型号的最新解释

	/*
	 .......  Original Mic-E (leading space)  (not message capable)
	>.......  TH-D7A walkie Talkie            (message capable)
	>......v  *future TH-D7A upgrade version
	].......  TM-D700 MObile Radio            (message capable)
	]......=  TM-D710 Mobile Radio            (message capable)
	]......v  *future D7xx models version
	>......=  TH-D72 walkie Talkie (Oct 2010) (message capable)

	NEW FORMATS FOR ALL FUTURE APPLICATIONS: (plus the exceptions above)

	`......Mv  OTHER Mic-E  - display "McE-msg" or "Mic-Emsg"
	'......Mv  OTHER Mic-E  - display "McE-trk" or "McTrackr"

	As combinations of "Mv" bytes are added to this table, manufacturers
	are encouraged to display the correct radio type if known.  The
	following additional bytes have been declared:

	`......_b  Yaesu VX-8 (b = SPACE) (message capable)
	`......_"  Yaesu FTM-350 (message capable)
	`......_#  Yaesu VX-8G   (message capable)   [new in 2010]
	'......|3  Byonics TinyTrack3 (TT3)
	'......|4  Byonics TinyTrack4 (TT4)
	T......\v  Hamhud  ? (awaiting confirmation?)
	T....../v  Argent  ? (awaiting confirmation?)
	T......~v  OTHER (to be used when all other "M" MFR's are used up)
	*/
	//20110126 add by:nestor 对设备型号进行解码，增加了D72、VX系列电台

    rig_type[0] = '\0';
    if (info[offset] != '\0' && (info[offset] == '>' || info[offset] == ']' || info[offset] == '`' || info[offset] == '\'')) {
        /* detected type code:     > TH-D7    ] TM-D700 */
    	//fprintf(stderr,"Detect rig type:%c %c %c %c %c %c %c %c\n",info[offset],info[offset+1],info[offset+2],info[offset+3],info[offset+4],
		//	info[offset+5],info[offset+6],info[offset+7],info[offset+8]);
	    if (info[offset] == '>')
		{
			if(strchr(&info[offset],'='))
				snprintf(rig_type,sizeof(rig_type)," TH-D72");
			else
				snprintf(rig_type,sizeof(rig_type)," TH-D7");
		}
        if (info[offset] == ']')
		{
  			if(strchr(&info[offset],'='))
				snprintf(rig_type,sizeof(rig_type)," TM-D710");
			else
				snprintf(rig_type,sizeof(rig_type)," TM-D700");
		}
        if (info[offset] == '`' )
		{
			char *ptr_rig=NULL;
			ptr_rig = strchr(&info[offset],'_');
			if(ptr_rig)
			{
				if(strchr(ptr_rig,' '))
					snprintf(rig_type,sizeof(rig_type)," VX-8");
				else if (strchr(ptr_rig,'"'))
					snprintf(rig_type,sizeof(rig_type)," FTM-350");
				else if (strchr(ptr_rig,'#'))
					snprintf(rig_type,sizeof(rig_type)," VX-8G");
				else
					snprintf(rig_type,sizeof(rig_type)," Yaesu?");
			}

		}
        if (info[offset] == '\'' )
		{
 			char *ptr_rig=NULL;
			ptr_rig = strchr(&info[offset],'|');
			if(ptr_rig)
			{
				if(strchr(ptr_rig,'3'))
					snprintf(rig_type,sizeof(rig_type)," TT3");
				else if (strchr(ptr_rig,'4'))
					snprintf(rig_type,sizeof(rig_type)," TT4");
				else
					snprintf(rig_type,sizeof(rig_type)," TT?");
			}

		}
		//fprintf(stderr,"%s",rig_type);

        offset++;
    }

    info_size = (int)strlen(info);
    /* search for compressed altitude in Mic-E data */  // {
    if (info_size >= offset+4 && info[offset+3] == '}') {  // {
        /* detected altitude  ___} */
        alt = ((((long)info[offset] - (long)33) * (long)91 +(long)info[offset+1] - (long)33) * (long)91
                    + (long)info[offset+2] - (long)33) - 10000;  // altitude in meters
        alt /= 0.3048;                                // altitude in feet, as in normal APRS

        //32808 is -10000 meters, or 10 km (deepest ocean), which is as low as a MIC-E
        //packet may go.  Upper limit is mostly a guess.
        if ( (alt > 500000) || (alt < -32809) ) {  // Altitude is whacko.  Skip it.
            offset += 4;
        }
        else {  // Altitude is ok
            snprintf(temp, sizeof(temp), " /A=%06ld",alt);
            offset += 4;
            strncat(new_info,
                temp,
                sizeof(new_info) - strlen(new_info));
        }
    }

    /* start of comment */
    if (strlen(rig_type) > 0) {
        snprintf(temp, sizeof(temp), "%s",rig_type);
        strncat(new_info,
            temp,
            sizeof(new_info) - strlen(new_info));
    }

    strncat(new_info,
        " Mic-E ",
        sizeof(new_info) - strlen(new_info));
    if (msgtyp == 0) {
        switch (msg) {
            case 1:
                strncat(new_info,
                    "Enroute",
                    sizeof(new_info) - strlen(new_info));
                break;

            case 2:
                strncat(new_info,
                    "In Service",
                    sizeof(new_info) - strlen(new_info));
                break;

            case 3:
                strncat(new_info,
                    "Returning",
                    sizeof(new_info) - strlen(new_info));
                break;

            case 4:
                strncat(new_info,
                    "Committed",
                    sizeof(new_info) - strlen(new_info));
                break;

            case 5:
                strncat(new_info,
                    "Special",
                    sizeof(new_info) - strlen(new_info));
                break;

            case 6:
                strncat(new_info,
                    "Priority",
                    sizeof(new_info) - strlen(new_info));
                break;

            case 7:
                strncat(new_info,
                    "Emergency",
                    sizeof(new_info) - strlen(new_info));

                // Functionality removed

                break;

            default:
                strncat(new_info,
                    "Off Duty",
                    sizeof(new_info) - strlen(new_info));
        }
    }
    else {
        snprintf(temp, sizeof(temp), "Custom%d",msg);
        strncat(new_info,
            temp,
            sizeof(new_info) - strlen(new_info));
    }

    if (info[offset] != '\0') {
        /* Append the rest of the message to the expanded MIC-E message */
        for (ii=offset; ii<info_size; ii++)
            temp[ii-offset] = info[ii];

        temp[info_size-offset] = '\0';
        strncat(new_info,
            " ",
            sizeof(new_info) - strlen(new_info));
        strncat(new_info,
            temp,
            sizeof(new_info) - strlen(new_info));
    }


    // We don't transmit Mic-E protocol from Xastir, so we know it's
    // not our station's packets or our object/item packets,
    // therefore the last two parameters here are both zero.
    //
    ok = data_add(APRS_MICE,call_sign,path,new_info,port,NULL,third_party, 0, 0);
	//nestor
	//fprintf(stderr,"TNC Receive:%s,P:%s,N:%s,T:%i \n",call_sign,path,new_info,third_party);


    return(ok);
}   // End of decode_Mic_E()







void decode_info_field(gchar *call,
                       gchar *path,
                       gchar *message,
                       gchar *origin,
                       TAprsPort port,
                       gint third_party,
                       gchar *orig_message)
{
    int  ok_igate_net;
    int  ok_igate_rf;
    int  done, ignore;
    char data_id;
    int station_is_mine = 0;
    int object_is_mine = 0;

    /* remember fixed format starts with ! and can be up to 24 chars in the message */ // ???

    done         = 0;       // if 1, packet was decoded
    ignore       = 0;       // if 1, don't treat undecoded packets as status text
    ok_igate_net = 0;       // if 1, send packet to internet
    ok_igate_rf  = 0;       // if 1, igate packet to RF if "from" is in nws-stations.txt

    if ( is_my_call(call, 1) ) {
        station_is_mine++; // Station is controlled by me
    }

    if ( (message != NULL) && (strlen(message) > MAX_LINE_SIZE) ) {
    	// Overly long message, throw it away.
        done = 1;
    }
    else if (message == NULL || strlen(message) == 0) {
    	// we could have an empty message
        (void)data_add(STATION_CALL_DATA,call,path,NULL,port,origin,third_party, station_is_mine, 0);
        done = 1;
        // don't report it to internet
    }

    // special treatment for objects/items.
    if (!done && origin[0] != '\0') {

        // If station/object/item is owned by me (including SSID)
        if ( is_my_call(origin, 1) ) {
            object_is_mine++;
        }

        if (message[0] == '*') {    // set object
            (void)data_add(APRS_OBJECT,call,path,message+1,port,origin,third_party, station_is_mine, object_is_mine);
            if (strlen(origin) > 0 && strncmp(origin,"INET",4)!=0) {
                ok_igate_net = 1;   // report it to internet
            }
            ok_igate_rf = 1;
            done = 1;
        }

        else if (message[0] == '!') {
        	// set item
            (void)data_add(APRS_ITEM,call,path,message+1,port,origin,third_party, station_is_mine, object_is_mine);
            if (strlen(origin) > 0 && strncmp(origin,"INET",4)!=0) {
                ok_igate_net = 1;   // report it to internet
            }
            ok_igate_rf = 1;
            done = 1;
        }

        else if (message[0] == '_') {   // delete object/item
// TODO
/*
            AprsDataRow *p_station;

            delete_object(call);    // ?? does not vanish from map immediately !!???

            // If object was owned by me but another station is
            // transmitting it now, write entries into the
            // object.log file showing that we don't own this object
            // anymore.
            p_station = NULL;

            if (search_station_name(&p_station,call,1)) {
                if (is_my_object_item(p_station)    // If station was owned by me (including SSID)
                        && (!object_is_mine) ) {  // But isn't now
                    disown_object_item(call,origin);
                }
            }

            if (strlen(origin) > 0 && strncmp(origin,"INET",4)!=0) {
                ok_igate_net = 1;   // report it to internet
            }

            ok_igate_rf = 1;
*/
            done = 1;

        }
    }

    if (!done) {
        int rdf_type;

        data_id = message[0];           // look at the APRS Data Type ID (first char in information field)
        message += 1;                   // extract data ID from information field
        ok_igate_net = 1;               // as default report packet to internet


        switch (data_id) {
            case '=':   // Position without timestamp (with APRS messaging)

                //WE7U
                // Need to check for weather info in this packet type as well?

                done = data_add(APRS_MSGCAP,call,path,message,port,origin,third_party, station_is_mine, 0);
                ok_igate_rf = done;
                break;

            case '!':   // Position without timestamp (no APRS messaging) or Ultimeter 2000 WX
                if (message[0] == '!' && is_xnum_or_dash(message+1,40))   // Ultimeter 2000 WX
                    done = data_add(APRS_WX3,call,path,message+1,port,origin,third_party, station_is_mine, 0);
                else
                    done = data_add(APRS_FIXED,call,path,message,port,origin,third_party, station_is_mine, 0);
                ok_igate_rf = done;
                break;

            case '/':   // Position with timestamp (no APRS messaging)

//WE7U
// Need weather decode in this section similar to the '@' section
// below.

                if ((toupper(message[14]) == 'N' || toupper(message[14]) == 'S') &&
                    (toupper(message[24]) == 'W' || toupper(message[24]) == 'E')) { // uncompressed format
                    if (message[29] == '/') {
                        if (message[33] == 'g' && message[37] == 't')
                            done = data_add(APRS_WX1,call,path,message,port,origin,third_party, station_is_mine, 0);
                        else
                            done = data_add(APRS_MOBILE,call,path,message,port,origin,third_party, station_is_mine, 0);
                    }
                    else
                        done = data_add(APRS_DF,call,path,message,port,origin,third_party, station_is_mine, 0);
                }
                else {                                                // compressed format
                    if (message[16] >= '!' && message[16] <= 'z') {     // csT is speed/course
                        if (message[20] == 'g' && message[24] == 't')   // Wx data
                            done = data_add(APRS_WX1,call,path,message,port,origin,third_party, station_is_mine, 0);
                        else
                            done = data_add(APRS_MOBILE,call,path,message,port,origin,third_party, station_is_mine, 0);
                    }
                    else
                        done = data_add(APRS_DF,call,path,message,port,origin,third_party, station_is_mine, 0);
                }
//                done = data_add(APRS_DOWN,call,path,message,from,port,origin,third_party, station_is_mine, 0);
                ok_igate_rf = done;
                break;

            case '@':   // Position with timestamp (with APRS messaging)
                // DK7IN: could we need to test the message length first?
                if ((toupper(message[14]) == 'N' || toupper(message[14]) == 'S') &&
                    (toupper(message[24]) == 'W' || toupper(message[24]) == 'E')) {       // uncompressed format
                    if (message[29] == '/') {
                        if (message[33] == 'g' && message[37] == 't')
                            done = data_add(APRS_WX1,call,path,message,port,origin,third_party, station_is_mine, 0);
                        else
                            done = data_add(APRS_MOBILE,call,path,message,port,origin,third_party, station_is_mine, 0);
                    }
                    else
                        done = data_add(APRS_DF,call,path,message,port,origin,third_party, station_is_mine, 0);
                }
                else {                                                // compressed format
                    if (message[16] >= '!' && message[16] <= 'z') {     // csT is speed/course
                        if (message[20] == 'g' && message[24] == 't')   // Wx data
                            done = data_add(APRS_WX1,call,path,message,port,origin,third_party, station_is_mine, 0);
                        else
                            done = data_add(APRS_MOBILE,call,path,message,port,origin,third_party, station_is_mine, 0);
                    }
                    else
                        done = data_add(APRS_DF,call,path,message,port,origin,third_party, station_is_mine, 0);
                }
                ok_igate_rf = done;
                break;

            case '[':   // Maidenhead grid locator beacon (obsolete- but used for meteor scatter)
                done = data_add(APRS_GRID,call,path,message,port,origin,third_party, station_is_mine, 0);
                ok_igate_rf = done;
                break;
            case 0x27:  // Mic-E  Old GPS data (or current GPS data in Kenwood TM-D700)
            case 0x60:  // Mic-E  Current GPS data (but not used in Kennwood TM-D700)
            //case 0x1c:// Mic-E  Current GPS data (Rev. 0 beta units only)
            //case 0x1d:// Mic-E  Old GPS data (Rev. 0 beta units only)
                done = decode_Mic_E(call,path,message,port,third_party);
                ok_igate_rf = done;
                break;

            case '_':   // Positionless weather data                [APRS Reference, chapter 12]
                done = data_add(APRS_WX2,call,path,message,port,origin,third_party, station_is_mine, 0);
                ok_igate_rf = done;
                break;

            case '#':   // Peet Bros U-II Weather Station (km/h)    [APRS Reference, chapter 12]
                if (is_xnum_or_dash(message,13))
                    done = data_add(APRS_WX4,call,path,message,port,origin,third_party, station_is_mine, 0);
                ok_igate_rf = done;
                break;

            case '*':   // Peet Bros U-II Weather Station (mph)
                if (is_xnum_or_dash(message,13))
                    done = data_add(APRS_WX6,call,path,message,port,origin,third_party, station_is_mine, 0);
                ok_igate_rf = done;
                break;

            case '$':   // Raw GPS data or Ultimeter 2000
                if (strncmp("ULTW",message,4) == 0 && is_xnum_or_dash(message+4,44))
                    done = data_add(APRS_WX5,call,path,message+4,port,origin,third_party, station_is_mine, 0);
                else if (strncmp("GPGGA",message,5) == 0)
                    done = data_add(GPS_GGA,call,path,message,port,origin,third_party, station_is_mine, 0);
                else if (strncmp("GPRMC",message,5) == 0)
                    done = data_add(GPS_RMC,call,path,message,port,origin,third_party, station_is_mine, 0);
                else if (strncmp("GPGLL",message,5) == 0)
                    done = data_add(GPS_GLL,call,path,message,port,origin,third_party, station_is_mine, 0);
                else {
                        // handle VTG and WPT too  (APRS Ref p.25)
                }
                ok_igate_rf = done;
                break;

            case ':':   // Message

                // Do message logging if that feature is enabled.
                done = decode_message(call,path,message,port,third_party);

                // there could be messages I should not retransmit to internet... ??? Queries to me...
                break;

            case '>':   // Status                                   [APRS Reference, chapter 16]
                done = data_add(APRS_STATUS,call,path,message,port,origin,third_party, station_is_mine, 0);
                ok_igate_rf = done;
                break;

            case '?':   // Query
                done = process_query(call,path,message,port,third_party);
                ignore = 1;     // don't treat undecoded packets as status text
                break;

            case 'T':   // Telemetry data                           [APRS Reference, chapter 13]
                // We treat these as status packets currently.
                ok_igate_rf = 1;
                done = data_add(APRS_STATUS,call,path,message,port,origin,third_party, station_is_mine, 0);
                break;

            case '{':   // User-defined APRS packet format     //}
                // We treat these as status packets currently.
                ok_igate_rf = 1;
                break;

            case '<':   // Station capabilities                     [APRS Reference, chapter 15]
                //
                // We could tweak the Incoming Data dialog to add
                // filter togglebuttons.  One such toggle could be
                // "Station Capabilities".  We'd then have a usable
                // dialog for displaying things like ?IGATE?
                // responses.  In this case we wouldn't have to do
                // anything special with the packet for decoding,
                // just let it hit the default block below for
                // putting them into the status field of the record.
                // One downside is that we'd only be able to catch
                // new station capability records in that dialog.
                // The only way to look at past capability records
                // would be the Station Info dialog for each
                // station.
                //
                //fprintf(stderr,"%10s:  %s\n", call, message);

                // Don't set "done" as we want these to appear in
                // the status text for the record.
                break;

            case '%':   // Agrelo DFJr / MicroFinder Radio Direction Finding

                // Here is where we'd add a call to an RDF decode
                // function so that we could display vectors on the
                // map for each RDF position.

//
// Agrelo format:  "%XXX/Q<cr>"
//
// "XXX" is relative bearing to the signal (000-359).  Careful here:
// At least one unit reports in magnetic instead of relative
// degrees.  "000" means no direction info available, 360 means true
// north.
//
// "Q" is bearing quality (0-9).  0 = unsuitable.  9 = manually
// entered.  1-8 = varying quality with 8 being the best.
//
// I've also seen these formats, which may not be Agrelo compatible:
//
//      "%136.0/9"
//      "%136.0/8/158.0" (That last number is magnetic bearing)
//
// These sentences may be sent MULTIPLE times per second, like 20 or
// more!  If we decide to average readings, we'll need to dump our
// averages and start over if our course changes.
//

                // Check for Agrelo format:
                if (    strlen(message) >= 5
                        && is_num_chr(message[0])   // "%136/9"
                        && is_num_chr(message[1])
                        && is_num_chr(message[2])
                        && message[3] == '/'
                        && is_num_chr(message[4]) ) {

                    rdf_type = 1;

                    fprintf(stderr,
                        "Type 1 RDF packet from call: %s\tBearing: %c%c%c\tQuality: %c\n",
                        call,
                        message[0],
                        message[1],
                        message[2],
                        message[4]);

                }

                // Check for extended formats (not
                // Agrelo-compatible):
                else if (strlen(message) >= 13
                        && is_num_chr(message[0])   // "%136.0/8/158.0"
                        && is_num_chr(message[1])
                        && is_num_chr(message[2])
                        && message[3] == '.'
                        && is_num_chr(message[4])
                        && message[5] == '/'
                        && is_num_chr(message[6])
                        && message[7] == '/'
                        && is_num_chr(message[8])
                        && is_num_chr(message[9])
                        && is_num_chr(message[10])
                        && message[11] == '.'
                        && is_num_chr(message[12]) ) {

                    rdf_type = 3;

                    fprintf(stderr,
                        "Type 3 RDF packet from call: %s\tBearing: %c%c%c%c%c\tQuality: %c\tMag Bearing: %c%c%c%c%c\n",
                        call,
                        message[0],
                        message[1],
                        message[2],
                        message[3],
                        message[4],
                        message[6],
                        message[8],
                        message[9],
                        message[10],
                        message[11],
                        message[12]);
                }

                // Check for extended formats (not
                // Agrelo-compatible):
                else if (strlen(message) >= 7
                        && is_num_chr(message[0])   // "%136.0/9"
                        && is_num_chr(message[1])
                        && is_num_chr(message[2])
                        && message[3] == '.'
                        && is_num_chr(message[4])
                        && message[5] == '/'
                        && is_num_chr(message[6]) ) {

                    rdf_type = 2;

                    fprintf(stderr,
                        "Type 2 RDF packet from call: %s\tBearing: %c%c%c%c%c\tQuality: %c\n",
                        call,
                        message[0],
                        message[1],
                        message[2],
                        message[3],
                        message[4],
                        message[6]);
                }

                // Don't set "done" as we want these to appear in
                // the status text for the record until we get the
                // full decoding for this type of packet coded up.
                break;

            case '~':   // UI-format messages, not relevant for APRS ("Do not use" in Reference)
            case ',':   // Invalid data or test packets             [APRS Reference, chapter 19]
            case '&':   // Reserved -- Map Feature
                ignore = 1;     // Don't treat undecoded packets as status text
                break;
        }


        // Add most remaining data to the station record as status
        // info
        //
        if (!done && !ignore) {         // Other Packets        [APRS Reference, chapter 19]
            done = data_add(OTHER_DATA,call,path,message-1,port,origin,third_party, station_is_mine, 0);
            ok_igate_net = 0;           // don't put data on internet       ????
        }

        if (!done) {                    // data that we do ignore...
            //fprintf(stderr,"decode_info_field: not decoding info: Call:%s ID:%c Msg:|%s|\n",call,data_id,message);
            ok_igate_net = 0;           // don't put data on internet
        }
    }

    if (third_party)
        ok_igate_net = 0;   // don't put third party traffic on internet


    if (station_is_mine)
        ok_igate_net = 0;   // don't put my data on internet     ???


    // TODO - Add TX to IGate support
/*
    if (ok_igate_net) {

        if ( (from == DATA_VIA_TNC) // Came in via a TNC
                && (strlen(orig_message) > 0) ) { // Not empty

            // Here's where we inject our own callsign like this:
            // "WE7U-15,I" in order to provide injection ID for our
            // igate.
            snprintf(line,
                sizeof(line),
                "%s>%s,%s,I:%s",
                (strlen(origin)) ? origin : call,
                path,
                _aprs_mycall,
                orig_message);

            output_igate_net(line, port, third_party);
        }
    }
*/


}





void decode_waypoints(gchar *data_string)	//20110121 add by:nestor 支持 waypoints
{
	gchar call_sign[20],pos_info[100],pos_extend[100],symbol_sign;
	int pos,i,len;
	/*
	NMEA
	$GPWPL,3852.92,N,12129.99,E,BD2STH-9*D5
	$GPWPL,3854.40,N,12137.89,E,BD2RLT-7*D9
	$GPWPL,3852.92,N,12129.99,E,BD2STH-9*D5
	$GPWPL,3854.40,N,12137.89,E,BD2RLT-7*D9
	$GPWPL,3854.40,N,12137.89,E,BD2RLT-7*D9
	KENWOOD
	$PKWDWPL,033400,V,3854.40,N,12137.89,E,0,0,190111,,BD2RLT-7,\g*8A
	$PKWDWPL,033600,V,3852.92,N,12129.99,E,0,66,190111,000076,BD2STH-9,/>*9F
	$PKWDWPL,033600,V,3854.40,N,12137.89,E,0,0,190111,,BD2RLT-7,\g*88
	MAG
	$PMGNWPL,3852.92,N,12129.99,E,000072,M,BD2STH-9,,,*B2
	$PMGNWPL,3854.40,N,12137.89,E,,BD2RLT-7,,,*DA
	$PMGNWPL,3852.92,N,12129.99,E,000076,M,BD2STH-9,,,*B6
	*/
	symbol_sign = '\\';
	*pos_extend = 'r';
	if(!strncmp(data_string,"$GPWPL",6))		//NMEA格式
	{
		//取呼号
		//指向pos
		pos = 7;
		strcpy(pos_extend+1,"NMEA Waypoints");

	}
	if(!strncmp(data_string,"$PKWDWPL",8))	//KENWOOD 格式
	{

		pos = 9;
		while(*(data_string + pos)!='V')
		{
			pos++;
		}
		pos++;
		len = strlen(data_string);
		while(*(data_string+len))
		{
			if(*(data_string+len)=='*' || len<=2)
				break;
			len --;
		}
		//fprintf(stderr,"len:%i pos:%s\n",len,data_string + len);

		strcpy(pos_extend+1,"KENWOOD Waypoints");
		//健伍格式下要提取APRS符号
		if(*(data_string + len - 2) != '\\')
			symbol_sign = '/';

		*pos_extend = *(data_string + len - 1);

	}
	if(!strncmp(data_string,"$PMGNWPL",8))	//麦哲伦格式
	{

		pos = 9;
		strcpy(pos_extend+1,"MAGELLAN Waypoints");
	}

	i=0;
	while(*(data_string + pos))
	{
		if(*(data_string + pos) == ',')
		{
			if(*(data_string + pos - 1) == 'N')
			{
				*(data_string + pos)=symbol_sign;
			}
			else
			{
				pos++;
			}
		}
		pos_info[i]= *(data_string + pos );
		i++;
		pos++;
		if(*(data_string + pos - 1) == 'E')
			break;
	}
	pos_info[i]='\0';
	//找到呼号首字母位置
	while(*(data_string + pos))
	{
		if((*(data_string + pos) >='a' && *(data_string + pos)<='z') ||
			(*(data_string + pos) >='A' && *(data_string + pos)<='Z') )
		{
			if(*(data_string + pos + 1)==',')
				pos ++;
			else
				break;
		}
		else
			pos ++;


	}
	//取呼号
	i=0;
	while((*(data_string + pos) >='a' && *(data_string + pos)<='z') ||
			(*(data_string + pos) >='A' && *(data_string + pos)<='Z') ||
			(*(data_string + pos) >='0' && *(data_string + pos)<='9') ||
			*(data_string + pos) =='-' )
	{
		call_sign[i] = *(data_string + pos);
		i++;
		pos++;
	}

	call_sign[i]='\0';
	strcat(pos_info,pos_extend);
	//fprintf(stderr,"wp call:%s___pos:%s\n",call_sign,pos_info);
	data_add(APRS_MICE,call_sign,NULL,pos_info,APRS_PORT_TTY,NULL,0, 0, 0);

	//
	//如果处于转发模式，则转发至互联网
	/*转移到了data_add后面
	int is_offset,is_gpsout,is_igate;
	sscanf(_aprs_beacon_comment,"offset=%i;gpsout=%i;igate=%i",&is_offset,&is_gpsout,&is_igate);
	if(is_igate==1 || is_igate==3)
	{
		fprintf(stderr,"trans to inet\n");
		gchar *packet;
	 	packet = g_strdup_printf("=%s",pos_info);
		output_igate_aprs_data(APRS_PORT_INET,call_sign, NULL, packet, strlen(packet));
	}
	*/
}

int valid_object(char *name) {
    int len, i;

    // max 9 printable ASCII characters, case sensitive   [APRS
    // Reference]
    len = (int)strlen(name);
    if (len > 9 || len == 0)
        return(0);                      // wrong size

    for (i=0;i<len;i++)
        if (!isprint((int)name[i]))
            return(0);                  // not printable

    return(1);
}


int extract_object(char *call, char **info, char *origin)
{
    int ok, i;

    // Object and Item Reports     [APRS Reference, chapter 11]
    ok = 0;
    // todo: add station originator to database
    if ((*info)[0] == ';') {                    // object
        // fixed 9 character object name with any printable ASCII character
        if (strlen((*info)) > 1+9) {
            substr(call,(*info)+1,9);           // extract object name
            (*info) = (*info) + 10;
            // Remove leading spaces ? They look bad, but are allowed by the APRS Reference ???
            (void)remove_trailing_spaces(call);
            if (valid_object(call)) {
                // info length is at least 1
                ok = 1;
            }
        }
    }
    else if ((*info)[0] == ')') {             // item
        // 3 - 9 character item name with any printable ASCII character
        if (strlen((*info)) > 1+3) {
            for (i = 1; i <= 9; i++) {
                if ((*info)[i] == '!' || (*info)[i] == '_') {
                    call[i-1] = '\0';
                    break;
                }
                call[i-1] = (*info)[i];
            }
            call[9] = '\0';  // In case we never saw '!' || '_'
            (*info) = &(*info)[i];
            // Remove leading spaces ? They look bad, but are allowed by the APRS Reference ???
            //(void)remove_trailing_spaces(call);   // This statement messed up our searching!!! Don't use it!
            if (valid_object(call)) {
                // info length is at least 1
                ok = 1;
            }
        }
    }
    else
    {
        fprintf(stderr,"Not an object, nor an item!!! call=%s, info=%s, origin=%s.\n",
               call, *info, origin);
    }
    return(ok);
}

int valid_call(char *call) {
    int len, ok;
    int i, del, has_num, has_chr;
    char c;

    has_num = 0;
    has_chr = 0;
    ok      = 1;
    len = (int)strlen(call);

    if (len == 0)
        return(0);                              // wrong size

    while (call[0]=='c' && call[1]=='m' && call[2]=='d' && call[3]==':') {
        // Erase TNC prompts from beginning of callsign.  This may
        // not be the right place to do this, but it came in handy
        // here, so that's where I put it. -- KB6MER

        for(i=0; call[i+4]; i++)
            call[i]=call[i+4];

        call[i++]=0;
        call[i++]=0;
        call[i++]=0;
        call[i++]=0;
        len=strlen(call);

    }

    if (len > 9)
        return(0);      // Too long for valid call (6-2 max e.g. KB6MER-12)

    del = 0;
    for (i=len-2;ok && i>0 && i>=len-3;i--) {   // search for optional SSID
        if (call[i] =='-')
            del = i;                            // found the delimiter
    }
    if (del) {                                  // we have a SSID, so check it
        if (len-del == 2) {                     // 2 char SSID
            if (call[del+1] < '1' || call[del+1] > '9')                         //  -1 ...  -9
                del = 0;
        }
        else {                                  // 3 char SSID
            if (call[del+1] != '1' || call[del+2] < '0' || call[del+2] > '5')   // -10 ... -15
                del = 0;
        }
    }

    if (del)
        len = del;                              // length of base call

    for (i=0;ok && i<len;i++) {                 // check for uppercase alphanumeric
        c = call[i];

        if (c >= 'A' && c <= 'Z')
            has_chr = 1;                        // we need at least one char
        else if (c >= '0' && c <= '9')
            has_num = 1;                        // we need at least one number
        else
            ok = 0;                             // wrong character in call
    }

//    if (!has_num || !has_chr)                 // with this we also discard NOCALL etc.
    if (!has_chr)
        ok = 0;

    ok = (ok && strcmp(call,"NOCALL") != 0);    // check for errors
    ok = (ok && strcmp(call,"ERROR!") != 0);
    ok = (ok && strcmp(call,"WIDE")   != 0);
    ok = (ok && strcmp(call,"RELAY")  != 0);
    ok = (ok && strcmp(call,"MAIL")   != 0);

    return(ok);
}

int valid_inet_name(char *name, char *info, char *origin, int origin_size) {
    int len, i, ok;
    char *ptr;

    len = (int)strlen(name);

    if (len > 9 || len == 0)            // max 9 printable ASCII characters
        return(0);                      // wrong size

    for (i=0;i<len;i++)
        if (!isprint((int)name[i]))
            return(0);                  // not printable

    // Modifies "origin" if a match found
    //
    if (len >= 5 && strncmp(name,"aprsd",5) == 0) {
        snprintf(origin, origin_size, "INET");
        origin[4] = '\0';   // Terminate it
        return(1);                      // aprsdXXXX is ok
    }

    // Modifies "origin" if a match found
    //
    if (len == 6) {                     // check for NWS
        ok = 1;
        for (i=0;i<6;i++)
            if (name[i] <'A' || name[i] > 'Z')  // 6 uppercase characters
                ok = 0;
        ok = ok && (info != NULL);      // check if we can test info
        if (ok) {
            ptr = strstr(info,":NWS-"); // "NWS-" in info field (non-compressed alert)
            ok = (ptr != NULL);

            if (!ok) {
                ptr = strstr(info,":NWS_"); // "NWS_" in info field (compressed alert)
                ok = (ptr != NULL);
            }
        }
        if (ok) {
            snprintf(origin, origin_size, "INET-NWS");
            origin[8] = '\0';
            return(1);                      // weather alerts
        }
    }

    return(1);  // Accept anything else if we get to this point in
                // the code.  After all, the message came from the
                // internet, not from RF.
}

int valid_path(char *path) {
    int i,len,hops,j;
    int type,ast,allast,ins;
    char ch;


    len  = (int)strlen(path);
    type = 0;       // 0: unknown, 1: AEA '>', 2: TAPR2 ',', 3: mixed
    hops = 1;
    ast  = 0;
    allast = 0;

    // There are some multi-port TNCs that deliver the port at the end
    // of the path. For now we discard this information. If there is
    // multi-port TNC support some day, we should write the port into
    // our database.
    // KAM:        /V /H
    // KPC-9612:   /0 /1 /2
    if (len > 2 && path[len-2] == '/') {
        ch = path[len-1];
        if (ch == 'V' || ch == 'H' || ch == '0' || ch == '1' || ch == '2') {
            path[len-2] = '\0';
            len  = (int)strlen(path);
        }
    }


    // One way of adding igate injection ID is to add "callsign-ssid,I".
    // We need to remove the ",I" portion so it doesn't count as another
    // digi here.  This should be at the end of the path.
    if (len > 2 && path[len-2] == ',' && path[len-1] == 'I') {  // Found ",I"
        //fprintf(stderr,"%s\n",path);
        //fprintf(stderr,"Found ',I'\n");
        path[len-2] = '\0';
        len  = (int)strlen(path);
        //fprintf(stderr,"%s\n\n",path);
    }
    // Now look for the same thing but with a '*' character at the end.
    // This should be at the end of the path.
    if (len > 3 && path[len-3] == ',' && path[len-2] == 'I' && path[len-1] == '*') {  // Found ",I*"
        //fprintf(stderr,"%s\n",path);
        //fprintf(stderr,"Found ',I*'\n");
        path[len-3] = '\0';
        len  = (int)strlen(path);
        //fprintf(stderr,"%s\n\n",path);
    }


    // Another method of adding igate injection ID is to add a '0' in front of
    // the SSID.  For WE7U it would change to WE7U-00, for WE7U-15 it would
    // change to WE7U-015.  Take out this zero so the rest of the decoding will
    // work.  This should be at the end of the path.
    // Also look for the same thing but with a '*' character at the end.
    if (len > 6) {
        for (i=len-1; i>len-6; i--) {
            if (path[i] == '-' && path[i+1] == '0') {
                //fprintf(stderr,"%s\n",path);
                for (j=i+1; j<len; j++) {
                    path[j] = path[j+1];    // Shift everything left by one
                }
                len = (int)strlen(path);
                //fprintf(stderr,"%s\n\n",path);
            }
            // Check whether we just chopped off the '0' from "-0".
            // If so, chop off the dash as well.
            if (path[i] == '-' && path[i+1] == '\0') {
                //fprintf(stderr,"%s\tChopping off dash\n",path);
                path[i] = '\0';
                len = (int)strlen(path);
                //fprintf(stderr,"%s\n",path);
            }
            // Check for "-*", change to '*' only
            if (path[i] == '-' && path[i+1] == '*') {
                //fprintf(stderr,"%s\tChopping off dash\n",path);
                path[i] = '*';
                path[i+1] = '\0';
                len = (int)strlen(path);
                //fprintf(stderr,"%s\n",path);
            }
            // Check for "-0" or "-0*".  Change to "" or "*".
            if ( path[i] == '-' && path[i+1] == '0' ) {
                //fprintf(stderr,"%s\tShifting left by two\n",path);
                for (j=i; j<len; j++) {
                    path[j] = path[j+2];    // Shift everything left by two
                }
                len = (int)strlen(path);
                //fprintf(stderr,"%s\n",path);
            }
        }
    }


    for (i=0,j=0; i<len; i++) {
        ch = path[i];

        if (ch == '>' || ch == ',') {   // found digi call separator
            // We're at the start of a callsign entry in the path

            if (ast > 1 || (ast == 1 && i-j > 10) || (ast == 0 && (i == j || i-j > 9))) {
                return(0);              // more than one asterisk in call or wrong call size
            }
            ast = 0;                    // reset local asterisk counter

            j = i+1;                    // set to start of next call
            if (ch == ',')
                type |= 0x02;           // set TAPR2 flag
            else
                type |= 0x01;           // set AEA flag (found '>')
            hops++;                     // count hops
        }

        else {                          // digi call character or asterisk
            // We're in the middle of a callsign entry

            if (ch == '*') {
                ast++;                  // count asterisks in call
                allast++;               // count asterisks in path
            }
            else if ((ch <'A' || ch > 'Z')      // Not A-Z
                    && (ch <'a' || ch > 'z')    // Not a-z
                    && (ch <'0' || ch > '9')    // Not 0-9
                    && ch != '-') {
                // Note that Q-construct and internet callsigns can
                // have a-z in them, AX.25 callsigns cannot unless
                // they are in a 3rd-party packet.

                return(0);          // wrong character in path
            }
        }
    }
    if (ast > 1 || (ast == 1 && i-j > 10) || (ast == 0 && (i == j || i-j > 9))) {
        return(0);                      // more than one asterisk or wrong call size
    }

    if (type == 0x03) {
        return(0);                      // wrong format, both '>' and ',' in path
    }

    if (hops > 9) {                     // [APRS Reference chapter 3]
        return(0);                      // too much hops, destination + 0-8 digipeater addresses
    }

    if (type == 0x01) {
        int delimiters[20];
        int k = 0;
        char dest[15];
        char rest[100];

        for (i=0; i<len; i++) {
            if (path[i] == '>') {
                path[i] = ',';          // Exchange separator character
                delimiters[k++] = i;    // Save the delimiter indexes
            }
        }

        // We also need to move the destination callsign to the end.
        // AEA has them in a different order than TAPR-2 format.
        // We'll move the destination address between delimiters[0]
        // and [1] to the end of the string.

        //fprintf(stderr,"Orig. Path:%s\n",path);
        // Save the destination
        snprintf(dest,sizeof(dest),"%s",&path[delimiters[--k]+1]);
        dest[strlen(path) - delimiters[k] - 1] = '\0'; // Terminate it
        dest[14] = '\0';    // Just to make sure
        path[delimiters[k]] = '\0'; // Delete it from the original path
        //fprintf(stderr,"Destination: %s\n",dest);

        // TAPR-2 Format:
        // KC2ELS-1*>SX0PWT,RELAY,WIDE:`2`$l##>/>"4)}
        //
        // AEA Format:
        // KC2ELS-1*>RELAY>WIDE>SX0PWT:`2`$l##>/>"4)}
        //          9     15   20

        // We now need to insert the destination into the middle of
        // the string.  Save part of it in another variable first.
        snprintf(rest,
            sizeof(rest),
            "%s",
            path);
        //fprintf(stderr,"Rest:%s\n",rest);
        snprintf(path,len+1,"%s,%s",dest,rest);
        //fprintf(stderr,"New Path:%s\n",path);
    }

    if (allast < 1) {                   // try to insert a missing asterisk
        ins  = 0;
        hops = 0;

        for (i=0; i<len; i++) {

            for (j=i; j<len; j++) {             // search for separator
                if (path[j] == ',')
                    break;
            }

            if (hops > 0 && (j - i) == 5) {     // WIDE3
                if (  path[ i ] == 'W' && path[i+1] == 'I' && path[i+2] == 'D'
                   && path[i+3] == 'E' && path[i+4] >= '0' && path[i+4] <= '9') {
                    ins = j;
                }
            }

/*
Don't do this!  It can mess up relay/wide1-1 digipeating by adding
an asterisk later in the path than the first unused digi.
            if (hops > 0 && (j - i) == 7) {     // WIDE3-2
                if (  path[ i ] == 'W' && path[i+1] == 'I' && path[i+2] == 'D'
                   && path[i+3] == 'E' && path[i+4] >= '0' && path[i+4] <= '9'
                   && path[i+5] == '-' && path[i+6] >= '0' && path[i+6] <= '9'
                   && (path[i+4] != path[i+6]) ) {
                    ins = j;
                }
            }
*/

            if (hops > 0 && (j - i) == 6) {     // TRACE3
                if (  path[ i ] == 'T' && path[i+1] == 'R' && path[i+2] == 'A'
                   && path[i+3] == 'C' && path[i+4] == 'E'
                   && path[i+5] >= '0' && path[i+5] <= '9') {
                    if (hops == 1)
                        ins = j;
                    else
                        ins = i-1;
                }
            }

/*
Don't do this!  It can mess up relay/wide1-1 digipeating by adding
an asterisk later in the path than the first unused digi.
            if (hops > 0 && (j - i) == 8) {     // TRACE3-2
                if (  path[ i ] == 'T' && path[i+1] == 'R' && path[i+2] == 'A'
                   && path[i+3] == 'C' && path[i+4] == 'E' && path[i+5] >= '0'
                   && path[i+5] <= '9' && path[i+6] == '-' && path[i+7] >= '0'
                   && path[i+7] <= '9' && (path[i+5] != path[i+7]) ) {
                    if (hops == 1)
                        ins = j;
                    else
                        ins = i-1;
                }
            }
*/

            hops++;
            i = j;                      // skip to start of next call
        }
        if (ins > 0) {
            for (i=len;i>=ins;i--) {
                path[i+1] = path[i];    // generate space for '*'
                // we work on a separate path copy which is long enough to do it
            }
            path[ins] = '*';            // and insert it
        }
    }
    return(1);  // Path is good
}

int extract_third_party(char *call,
                        char *path,
                        int path_size,
                        char **info,
                        char *origin,
                        int origin_size) {
    int ok;
    char *p_call;
    char *p_path;

    p_call = NULL;                              // to make the compiler happy...
    p_path = NULL;                              // to make the compiler happy...
    ok = 0;
    if (!is_my_call(call,1)) { // Check SSID also
        // todo: add reporting station call to database ??
        //       but only if not identical to reported call
        (*info) = (*info) +1;                   // strip '}' character
        p_call = strtok((*info),">");           // extract call
        if (p_call != NULL) {
            p_path = strtok(NULL,":");          // extract path
            if (p_path != NULL) {
                (*info) = strtok(NULL,"");      // rest is information field
                if ((*info) != NULL)            // the above looks dangerous, but works on same string
                    if (strlen(p_path) < 100)
                        ok = 1;                 // we have found all three components
            }
        }
    }

    if (ok) {

        snprintf(path,
            path_size,
            "%s",
            p_path);

        ok = valid_path(path);                  // check the path and convert it to TAPR format
        // Note that valid_path() also removes igate injection identifiers

    }

    if (ok) {                                         // check callsign
        (void)remove_trailing_asterisk(p_call);       // is an asterisk valid here ???
        if (valid_inet_name(p_call,(*info),origin,origin_size)) { // accept some of the names used in internet
            // Treat it as object with special origin
            snprintf(call,
                MAX_CALLSIGN+1,
                "%s",
                p_call);
        }
        else if (valid_call(p_call)) {              // accept real AX.25 calls
            snprintf(call,
                MAX_CALLSIGN+1,
                "%s",
                p_call);
        }
        else {
            ok = 0;
        }
    }
    return(ok);
}


void extract_TNC_text(gchar *info) {
    int i,j,len;

    if (g_strncasecmp(info,"thenet ",7) == 0) {   // 1st match
        len = strlen(info)-1;
        for (i=7;i<len;i++) {
            if (info[i] == ')')
                break;
        }
        len++;
        if (i>7 && info[i] == ')' && info[i+1] == ' ') {        // found
            i += 2;
            for (j=0;i<=len;i++,j++) {
                info[j] = info[i];
            }
        }
    }
}











gint decode_ax25_line(gchar *line, TAprsPort port) {
    gchar *call_sign;
    gchar *path0;
    gchar path[100+1];           // new one, we may add an '*'
    gchar *info;
	/*下文为源程序
    gchar info_copy[MAX_LINE_SIZE+1];
    gchar call[MAX_CALLSIGN+1];
    gchar origin[MAX_CALLSIGN+1];
	*/
	//把他们都加上1，看看程序能否崩溃 201101270600 nestor
    gchar info_copy[MAX_LINE_SIZE+2];
    gchar call[MAX_CALLSIGN+2];
    gchar origin[MAX_CALLSIGN+2];

    gint ok;
    gint third_party;
//    gchar backup[MAX_LINE_SIZE+1];
//    gchar tmp_line[MAX_LINE_SIZE+1];
//    gchar tmp_path[100+1];
//    gchar *ViaCalls[10];
//	fprintf(stderr, "Enter decode_ax25_line\n");

	if(!strncmp(line,"$GPWPL",6) || !strncmp(line,"$PKWDWPL",8) || !strncmp(line,"$PMGNWPL",8))
	{
		decode_waypoints(line);

        return(FALSE);
	}

	if (line == NULL)
    {
        return(FALSE);
    }

    if ( strlen(line) > MAX_LINE_SIZE )
    {
        // Overly long message, throw it away.  We're done.
        return(FALSE);
    }

    if (line[strlen(line)-1] == '\n')           // better: look at other places,
                                                // so that we don't get it here...
        line[strlen(line)-1] = '\0';            // Wipe out '\n', to be sure
    if (line[strlen(line)-1] == '\r')
        line[strlen(line)-1] = '\0';            // Wipe out '\r'

    call_sign   = NULL;
    path0       = NULL;
    info        = NULL;
    origin[0]   = '\0';
    call[0]     = '\0';
    path[0]     = '\0';
    third_party = 0;

    // CALL>PATH:APRS-INFO-FIELD                // split line into components
    //     ^    ^
    ok = 0;
    call_sign = (gchar*)strtok(line,">");               // extract call from AX.25 line
    if (call_sign != NULL) {
        path0 = (gchar*)strtok(NULL,":");               // extract path from AX.25 line
        if (path0 != NULL) {
            info = (gchar*)strtok(NULL,"");             // rest is info_field
            if (info != NULL) {
                if ((info - path0) < 100) {     // check if path could be copied
                    ok = 1;                     // we have found all three components
                }
            }
        }
    }

    if (ok)
    {
        snprintf(path, sizeof(path), "%s", path0);

        snprintf(info_copy, sizeof(info_copy), "%s", info);

        ok = valid_path(path);                  // check the path and convert it to TAPR format
        // Note that valid_path() also removes igate injection identifiers
    }

    if (ok)
    {
        extract_TNC_text(info);                 // extract leading text from TNC X-1J4
        if (strlen(info) > 256)                 // first check if information field conforms to APRS specs
            ok = 0;                             // drop packets too long
    }

    if (ok)
    {                                                   // check callsign
        (void)remove_trailing_asterisk(call_sign);              // is an asterisk valid here ???

        if (valid_inet_name(call_sign,info,origin,sizeof(origin)))
        { // accept some of the names used in internet
            snprintf(call, sizeof(call), "%s", call_sign);
        }
        else if (valid_call(call_sign))
        {                     // accept real AX.25 calls
            snprintf(call, sizeof(call), "%s", call_sign);
        }
        else {
            ok = 0;
        }
    }

    if (ok && info[0] == '}')
    {
    	// look for third-party traffic
        ok = extract_third_party(call,path,sizeof(path),&info,origin,sizeof(origin)); // extract third-party data
        third_party = 1;
    }

    if (ok && (info[0] == ';' || info[0] == ')'))
    {
    	// look for objects or items
        snprintf(origin, sizeof(origin), "%s", call);

        ok = extract_object(call,&info,origin);                 // extract object data
    }

    if (ok)
    {
        // decode APRS information field, always called with valid call and path
        // info is a string with 0 - 256 bytes
        // fprintf(stderr,"dec: %s (%s) %s\n",call,origin,info);

       decode_info_field(call,
            path,
            info,
            origin,
            port,
            third_party,
            info_copy);
    }


    return(ok);
}
static gboolean aprs_parse_server_packet(gchar *packet)
{
    decode_ax25_line(packet, APRS_PORT_INET);

    g_free(packet);

    return FALSE;
}

/*aprs decode end*/



//nestor 20110117 增加偏移计算方法
void
aprs_get_map_offset(double *offsetlat,double *offsetlon)
{

	*offsetlat=_offset_lat;
	*offsetlon=_offset_lon;

}

void
cal_offset_correct(double fromlat,double fromlon,double *offsetlat,double *offsetlon)
{

	double value_temp,value,lat,lon;
	int i;

	*offsetlat=0;
	*offsetlon=0;
	lat=0;
	lon=0;
	value=-1;

	for(i=0;i<_OffsetDB_lines;i++)
	{
		value_temp = (double)aprs_calculate_distance(fromlat,fromlon , _offset_china[i].lat1, _offset_china[i].lon1);
		if(value_temp<value || value==-1)
		{
			value = value_temp;
			*offsetlat = _offset_china[i].lat2 - _offset_china[i].lat1;
			*offsetlon = _offset_china[i].lon2 - _offset_china[i].lon1;
		}
	}
}

//自动纠偏的调用函数开始 20100118 nestor
void
auto_offset_set()
{
	if (_aprs_offset_enable)
	{
		cal_offset_correct(_gps.lat,_gps.lon,&_offset_lat,&_offset_lon);
	}
	else
	{
		_offset_lat = 0;
		_offset_lon = 0;
	}
	//fprintf(stderr, "%s %lf %lf\n",_curr_repo->url,_offset_lat,_offset_lon);
}
//自动纠偏的调用函数结束 20100118 nestor
//自动纠偏的初始化函数开始 20110117 nestor
void auto_offset_init()
{

	FILE* fp;
	char buf[512],s[100];
	int i;


	if(!(fp = g_fopen("/home/user/MyDocs/.maps/aprs/offset.conf","r")))
	{
        fprintf(stderr,"Can not open /home/user/MyDocs/.maps/aprs/offset.conf\n");
	}
	else
	{

		//先计算文件有多少行
		i=0;
		while(fgets(buf,512,fp)!=NULL)
		{
			//fprintf(stderr,"fgets:= %s\n",buf);
			if (*buf!='*' && *buf!='$')	//注释把它略过去
			{
				i++;
			}
		}
		if (i==0)
		{
			return;
		}
		//申请内存
		_offset_china=(struct _OffsetDB *)malloc(sizeof(struct _OffsetDB)*i);
		fseek(fp,0L,SEEK_SET);

		//fprintf(stderr,"正在读入纠偏文件!\n");

		i=0;
		while(fgets(buf,512,fp)!=NULL)
		{
			//fprintf(stderr,"fgets:= %s\n",buf);
			if (*buf!='*' && *buf!='$')	//注释把它略过去
			{
				sscanf(buf,"%s\t%lf\t%lf\t%lf\t%lf",s,&(_offset_china[i].lat1),&(_offset_china[i].lon1),&(_offset_china[i].lat2),&(_offset_china[i].lon2));	//取出经纬度
				i++;
			}
		}
		_OffsetDB_lines=i;

		fprintf(stderr,"%i lines offset data read OK!\n",_OffsetDB_lines);
		//设置好偏移
		auto_offset_set();


		fclose(fp);
	}
}
//自动纠偏的初始化函数结束

//TNC start
//WE7U2
// We feed a raw 7-byte string into this routine.  It decodes the
// callsign-SSID and tells us whether there are more callsigns after
// this.  If the "asterisk" input parameter is nonzero it'll add an
// asterisk to the callsign if it has been digipeated.  This
// function is called by the decode_ax25_header() function.
//
// Inputs:  string          Raw input string
//          asterisk        1 = add "digipeated" asterisk
//
// Outputs: callsign        Processed string
//          returned int    1=more callsigns follow, 0=end of address field
//
gint decode_ax25_address(gchar *string, gchar *callsign, gint asterisk) {
    gint i,j;
    gchar ssid;
    gchar t;
    gint more = 0;
    gint digipeated = 0;

    // Shift each of the six callsign characters right one bit to
    // convert to ASCII.  We also get rid of the extra spaces here.
    j = 0;
    for (i = 0; i < 6; i++) {
        t = ((unsigned char)string[i] >> 1) & 0x7f;
        if (t != ' ') {
            callsign[j++] = t;
        }
    }

    // Snag out the SSID byte to play with.  We need more than just
    // the 4 SSID bits out of it.
    ssid = (unsigned char)string[6];

    // Check the digipeat bit
    if ( (ssid & 0x80) && asterisk)
        digipeated++;   // Has been digipeated

    // Check whether it is the end of the address field
    if ( !(ssid & 0x01) )
        more++; // More callsigns to come after this one

    // Snag the four SSID bits
    ssid = (ssid >> 1) & 0x0f;

    // Construct the SSID number and add it to the end of the
    // callsign if non-zero.  If it's zero we don't add it.
    if (ssid) {
        callsign[j++] = '-';
        if (ssid > 9) {
            callsign[j++] = '1';
        }
        ssid = ssid % 10;
        callsign[j++] = '0' + ssid;
    }

    // Add an asterisk if the packet has been digipeated through
    // this callsign
    if (digipeated)
        callsign[j++] = '*';

    // Terminate the string
    callsign[j] = '\0';

    return(more);
}


// Function which receives raw AX.25 packets from a KISS interface and
// converts them to a printable TAPR-2 (more or less) style string.
// We receive the packet with a KISS Frame End character at the
// beginning and a "\0" character at the end.  We can end up with
// multiple asterisks, one for each callsign that the packet was
// digipeated through.  A few other TNC's put out this same sort of
// format.
//
// Note about KISS & CRC's:  The TNC checks the CRC.  If bad, it
// drops the packet.  If good, it sends it to the computer WITHOUT
// the CRC bytes.  There's no way at the computer end to check
// whether the packet was corrupted over the serial channel between
// the TNC and the computer.  Upon sending a KISS packet to the TNC,
// the TNC itself adds the CRC bytes back on before sending it over
// the air.  In Xastir we can just assume that we're getting
// error-free packets from the TNC, ignoring possible corruption
// over the serial line.
//
// Some versions of KISS can encode the radio channel (for
// multi-port TNC's) in the command byte.  How do we know we're
// running those versions of KISS though?  Here are the KISS
// variants that I've been able to discover to date:
//
// KISS               No CRC, one radio port
//
// SMACK              16-bit CRC, multiport TNC's
//
// KISS-CRC
//
// 6-PACK
//
// KISS Multi-drop (Kantronics) 8-bit XOR Checksum, multiport TNC's (AGWPE compatible)
// BPQKISS (Multi-drop)         8-bit XOR Checksum, multiport TNC's
// XKISS (Kantronics)           8-bit XOR Checksum, multiport TNC's
//
// JKISS              (AGWPE and BPQ32 compatible)
//
// MKISS              Linux driver which supports KISS/BPQ and
//                    hardware handshaking?  Also Paccomm command to
//                    immediately enter KISS mode.
//
// FlexKISS           -,
// FlexCRC            -|-- These are all the same!
// RMNC-KISS          -|
// CRC-RMNC           -'
//
//
// It appears that none of the above protocols implement any form of
// hardware flow control.
//
//
// Compare this function with interface.c:process_ax25_packet() to
// see if we're missing anything important.
//
//
// Inputs:  data_string         Raw string (must be MAX_LINE_SIZE or bigger)
//          length              Length of raw string (may get changed here)
//
// Outputs: int                 0 if it is a bad packet,
//                              1 if it is good
//          data_string         Processed string
//
gint decode_ax25_header(
		gchar *data_string,
		gint *length) {
    gchar temp[20];
    gchar result[MAX_LINE_SIZE+100];
    gchar dest[15];
    gint i, ptr;
    gchar callsign[15];
    gchar more;
    gchar num_digis = 0;


    // Do we have a string at all?
    if (data_string == NULL)
        return(0);

    // Drop the packet if it is too long.  Note that for KISS packets
    // we can't use strlen() as there can be 0x00 bytes in the
    // data itself.
    if (*length > 1024) {
        data_string[0] = '\0';
        *length = 0;
        return(0);
    }

    // Start with an empty string for the result
    result[0] = '\0';

    ptr = 0;

    // Process the destination address
    for (i = 0; i < 7; i++)
        temp[i] = data_string[ptr++];
    temp[7] = '\0';
    more = decode_ax25_address(temp, callsign, 0); // No asterisk
    snprintf(dest,sizeof(dest),"%s",callsign);

    // Process the source address
    for (i = 0; i < 7; i++)
        temp[i] = data_string[ptr++];
    temp[7] = '\0';
    more = decode_ax25_address(temp, callsign, 0); // No asterisk

    // Store the two callsigns we have into "result" in the correct
    // order
    snprintf(result,sizeof(result),"%s>%s",callsign,dest);

    // Process the digipeater addresses (if any)
    num_digis = 0;
    while (more && num_digis < 8) {
        for (i = 0; i < 7; i++)
            temp[i] = data_string[ptr++];
        temp[7] = '\0';

        more = decode_ax25_address(temp, callsign, 1); // Add asterisk
        strncat(result,
            ",",
            sizeof(result) - strlen(result));

        strncat(result,
            callsign,
            sizeof(result) - strlen(result));
        num_digis++;
    }

    strncat(result,
        ":",
        sizeof(result) - strlen(result));


    // Check the Control and PID bytes and toss packets that are
    // AX.25 connect/disconnect or information packets.  We only
    // want to process UI packets in Xastir.


    // Control byte should be 0x03 (UI Frame).  Strip the poll-bit
    // from the PID byte before doing the comparison.
    if ( (data_string[ptr++] & (~0x10)) != 0x03) {
        return(0);
    }


    // PID byte should be 0xf0 (normal AX.25 text)
    if (data_string[ptr++] != 0xf0)
        return(0);


// WE7U:  We get multiple concatenated KISS packets sometimes.  Look
// for that here and flag when it happens (so we know about it and
// can fix it someplace earlier in the process).  Correct the
// current packet so we don't get the extra garbage tacked onto the
// end.
    for (i = ptr; i < *length; i++) {
        if (data_string[i] == KISS_FEND) {
            fprintf(stderr,"***Found concatenated KISS packets:***\n");
            data_string[i] = '\0';    // Truncate the string
            break;
        }
    }

    // Add the Info field to the decoded header info
    strncat(result,
        (char *)(&data_string[ptr]),
        sizeof(result) - strlen(result));

    // Copy the result onto the top of the input data.  Note that
    // the length can sometimes be longer than the input string, so
    // we can't just use the "length" variable here or we'll
    // truncate our string.  Make sure the data_string variable is
    // MAX_LINE_SIZE or bigger.
    //
    snprintf((char *)data_string,
        MAX_LINE_SIZE,
        "%s",
        result);

    // Write out the new length
    *length = strlen(result);

//fprintf(stderr,"%s\n",data_string);

    return(1);
}

















// Added by KB6MER for KAM XL(SERIAL_TNC_AUX_GPS) support
// buf is a null terminated string
// returns buf as a null terminated string after cleaning.
// Currently:
//    removes leading 'cmd:' prompts from TNC if needed
// Can be used to add any additional data cleaning functions desired.
// Currently only called for SERIAL_TNC_AUX_GPS, but could be added
// to other device routines to improve packet decode on other devices.
//
// Note that the length of "buf" can be up to MAX_DEVICE_BUFFER,
// which is currently set to 4096.
//
void tnc_data_clean(gchar *buf) {

    while (!strncmp(buf,"cmd:",4)) {
        int ii;

        // We're _shortening_ the string here, so we don't need to
        // know the length of the buffer unless it has no '\0'
        // terminator to begin with!  In that one case we could run
        // off the end of the string and get a segfault or cause
        // other problems.
        for (ii = 0; ; ii++) {
            buf[ii] = buf[ii+4];
            if (buf[ii] == '\0')
                break;
        }
    }
}



static gboolean aprs_parse_tty_packet(gchar *packet)
{
    decode_ax25_line(packet, APRS_PORT_TTY);

    g_free(packet);

    return FALSE;
}


static gboolean way_points_checksum(gchar *buf)
{
	gint csum = 0;
    gboolean is_ok = FALSE;
    if(*buf == '$')
	{
		gchar *sptr = buf + 1; /* Skip the $ */
		/* This is the beginning of a sentence; okay to parse. */
		while(*sptr && *sptr != '*')
			csum ^= *sptr++;

		/* If we're at a \0 (meaning there is no checksum), or if
		* the checksum is good, then parse the sentence. */
		if(!*sptr || csum == strtol(sptr + 1, NULL, 16))
		{
			if(*sptr)
			*sptr = '\0'; /* take checksum out of the buffer.*/
			is_ok = TRUE;
		}
	}
	return is_ok;
}


static gboolean kiss_parse_packet(gchar *data_string, gint data_length)
{

	int is_aprs12;
	is_aprs12=0;
	//unsigned char *data_string2="\xa6\xb0\xaa\xa4\xaa\x68\x60\x84\x8e\x64\xa8\xa2\x96\xf2\xae\x92\x88\x8a\x62\x40\x62\xae\x92\x88\x8a\x64\x40\x63\x03\xf0\x60\x31\x3b\x1c\x6c\x20\x30\x50\x2f\x5d\x22\x34\x27\x7d\x3d\x0d\x00";
	//fprintf(stderr, "Parse: %s\n", data_string);

	//memcpy(data_string,data_string2,47);
	/*
	int i;
	fprintf(stderr,"TNCrx:%lx%lx%lx%lx%lx%lx%lx%lx%lx%lx%lx%lx%lx%lx%lx%lx%lx%lx%lx%lx\n",
		(int)(data_string[0]),(int)(data_string[1]),(int)(data_string[2]),(int)(data_string[3]),(int)(data_string[4]),(int)(data_string[5]),
		(int)(data_string[6]),(int)(data_string[7]),(int)(data_string[8]),(int)(data_string[9]),
		(int)(data_string[10]),(int)(data_string[11]),(int)(data_string[12]),(int)(data_string[13]),(int)(data_string[14]),(int)(data_string[15]),
		(int)(data_string[16]),(int)(data_string[17]),(int)(data_string[18]),(int)(data_string[19]));
	fprintf(stderr,"TNCrx:%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c\n",
		data_string[0],data_string[1],data_string[2],data_string[3],data_string[4],data_string[5],
		data_string[6],data_string[7],data_string[8],data_string[9],
		data_string[10],data_string[11],data_string[12],data_string[13],data_string[14],data_string[15],
		data_string[16],data_string[17],data_string[18],data_string[19]);
	fprintf(stderr,"TNCrx:");
	for(i=0;i<60;i++)
		fprintf(stderr,"%lx",(int)(data_string[i]));
	fprintf(stderr,"\nTNCrx:");
	for(i=0;i<60;i++)
		fprintf(stderr,"%c",data_string[i]);
	fprintf(stderr,"\n");
	*/





	gint devicetype = port_data.device_type;
	//2011年1月7日，把"cmd:cmd:"这样的字符串给清理了，这样D710就全解码了
	if(!strncmp(data_string,"cmd:",4))
		{

			tnc_data_clean((char *)data_string);
		};


	/*20100420 by:nestor,我们需要在这里拦截D710发来的字符串，如果属于呼号信息的，组装为ax_25的packet，供下面程序执行*/
	//fprintf(stderr, "one: %c\n", data_string[0]);
	if(data_string[0]=='B' )
		{

			/*识别出来了，就是D710发送的aprs信息*/
			//fprintf(stderr, "Block Ok!\n");
			//fprintf(stderr, "TNC Receive: %s\n", data_string);
			is_aprs12=1;
		};
	//识别航点格式
	if(!strncmp(data_string,"$GPWPL",6) || !strncmp(data_string,"$PKWDWPL",8) || !strncmp(data_string,"$PMGNWPL",8))
	{
		//支持航点输入 20110121
		if(way_points_checksum(data_string))
		{
			//fprintf(stderr, "checksum good waypoints: %s\n", data_string);
			//fprintf(stderr, "TNC Receive: %s\n", data_string);
			g_idle_add((GSourceFunc)aprs_parse_tty_packet, data_string);
			return TRUE;
		}
		//fprintf(stderr, "checksum bad: %s\n", data_string);

		//decode_waypoints(data_string);
	}
	if(*data_string == '$' && !_enable_gps)		//识别GPS 信息 & 2010.10.27 支持AVRTT
	{
		//Kenwood D710 发送了GPS信息串
		//fprintf(stderr, "Read gps data from d710!\n");
		*data_string='#';
		//gps_parse_rmc_kiss(data_string);
		//gtk_widget_show_all(GTK_WIDGET(_gps_widget));
		//map_move_mark();
		//gps_show_info();
		//map_refresh_mark(TRUE);

	}

	/*代码添加结束20100420 by:nestor*/

	switch(devicetype)
	{
    case DEVICE_SERIAL_KISS_TNC:
    case DEVICE_SERIAL_MKISS_TNC:
		if(is_aprs12==0)
			{
	   			 if ( !decode_ax25_header( data_string,
	            				&data_length ) ) {
	        			// Had a problem decoding it.  Drop
	        			// it on the floor.
					g_free(data_string);
	       			 break;
	    				}
	    			else {
	        			// Good decode.  Drop through to the
	        			// next block to log and decode the
	        			// packet.
	    				}
			};

	case DEVICE_SERIAL_TNC:
	    tnc_data_clean((char *)data_string);

	case DEVICE_AX25_TNC:




//     fprintf(stderr, "Decoded kiss: %s\n", data_string);
	 //fprintf(stderr, "TNC Receive: %s\n", data_string);
     g_idle_add((GSourceFunc)aprs_parse_tty_packet, data_string);
//		decode_ax25_line(data_string, "T", 0);
		break;

	default:
		break;
	}



	return FALSE;
}

// Add one record to the circular queue.  Returns 1 if queue is
// full, 0 if successful.
//
/*
int push_incoming_data(unsigned char *data_string, int length) {

    int next_write_ptr = (incoming_write_ptr + 1) % MAX_INPUT_QUEUE;

    // Check whether queue is full
    if (incoming_read_ptr == next_write_ptr) {
        // Yep, it's full!
        return(1);
    }

    // Advance the write pointer
    incoming_write_ptr = next_write_ptr;

    incoming_data_queue[incoming_write_ptr].length = length;

//    incoming_data_queue[incoming_write_ptr].port = port;

    snprintf((char *)incoming_data_queue[incoming_write_ptr].data,
        (length < MAX_LINE_SIZE) ? length : MAX_LINE_SIZE,
        "%s",
        data_string);

    queue_depth++;
    push_count++;

    return(0);
}
*/









//***********************************************************
// channel_data()
//
// Takes data read in from a port and adds it to the
// incoming_data_queue.  If queue is full, waits for queue to have
// space before continuing.
//
// port #
// string is the string of data
// length is the length of the string.  If 0 then use strlen()
// on the string itself to determine the length.
//
// Note that decode_ax25_header() and perhaps other routines may
// increase the length of the string while processing.  We need to
// send a COPY of our input string off to the decoding routines for
// this reason, and the size of the buffer must be MAX_LINE_SIZE
// for this reason also.
//***********************************************************
void channel_data(gchar *string, int length) {
    int max;
//    struct timeval tmv;
    // Some messiness necessary because we're using xastir_mutex's
    // instead of pthread_mutex_t's.
    int process_it = 0;


    //fprintf(stderr,"channel_data: %x %d\n",string[0],length);


    max = 0;

    if (string == NULL)
    {
        return;
    }

    if (string[0] == '\0')
    {
        return;
    }

    if (length == 0) {
        // Compute length of string including terminator
        length = strlen((const char *)string) + 1;
    }

    // Check for excessively long packets.  These might be TCP/IP
    // packets or concatenated APRS packets.  In any case it's some
    // kind of garbage that we don't want to try to parse.

    // Note that for binary data (WX stations and KISS packets), the
    // strlen() function may not work correctly.
    if (length > MAX_LINE_SIZE) {   // Too long!
//    	fprintf(stderr, "Too long");
        string[0] = '\0';   // Truncate it to zero length
        return;
    }


    // This protects channel_data from being run by more than one
    // thread at the same time.

    if (length > 0) {


        // Install the cleanup routine for the case where this
        // thread gets killed while the mutex is locked.  The
        // cleanup routine initiates an unlock before the thread
        // dies.  We must be in deferred cancellation mode for the
        // thread to have this work properly.  We must first get the
        // pthread_mutex_t address.



        // If it's any of three types of GPS ports and is a GPRMC or
        // GPGGA string, just stick it in one of two global
        // variables for holding such strings.  UpdateTime() can
        // come along and process/clear-out those strings at the
        // gps_time interval.
        //
        process_it++;

        // Remove the cleanup routine for the case where this thread
        // gets killed while the mutex is locked.  The cleanup
        // routine initiates an unlock before the thread dies.  We
        // must be in deferred cancellation mode for the thread to
        // have this work properly.
  //      pthread_cleanup_pop(0);


//fprintf(stderr,"Channel data on Port [%s]\n",(char *)string);

        if (process_it) {

            // Wait for empty space in queue
//fprintf(stderr,"\n== %s", string);


/*
            while (push_incoming_data(string, length) && max < 5400) {
                sched_yield();  // Yield to other threads
                tmv.tv_sec = 0;
                tmv.tv_usec = 2;  // 2 usec
                (void)select(0,NULL,NULL,NULL,&tmv);
                max++;
            }
*/

        	kiss_parse_packet(g_strdup(string), length);
            //g_idle_add((GSourceFunc)kiss_parse_packet, g_strdup(string));


        }
//        else
//        {
//        	fprintf(stderr,"Channel data on Port [%s]\n",(char *)string);
//        }
    }

}




//****************************************************************
// get device name only (the portion at the end of the full path)
// device_name current full name of device
//****************************************************************

char *get_device_name_only(char *device_name) {
    int i,len,done;

    if (device_name == NULL)
        return(NULL);

    done = 0;
    len = (int)strlen(device_name);
    for(i = len; i > 0 && !done; i--){
        if(device_name[i] == '/'){
            device_name += (i+1);
            done = 1;
        }
    }
    return(device_name);
}


int filethere(char *fn) {
    FILE *f;
    int ret;

    ret =0;
    f=fopen(fn,"r");
    if (f != NULL) {
        ret=1;
        (void)fclose(f);
    }
    return(ret);
}



/*
 * Close the serial port
 * */
gint serial_detach() {
    int ok;
    ok = -1;

    if (port_data.active == DEVICE_IN_USE && port_data.status == DEVICE_UP)
    {
        // Close port first
        (void)tcsetattr(port_data.channel, TCSANOW, &port_data.t_old);
        if (close(port_data.channel) == 0)
        {
            port_data.status = DEVICE_DOWN;
            //usleep(200);
            port_data.active = DEVICE_NOT_IN_USE;
            ok = 1;

        }
        else
        {
            fprintf(stderr,"Could not close port %s\n",port_data.device_name);

            port_data.status = DEVICE_DOWN;
            //usleep(200);
            port_data.active = DEVICE_NOT_IN_USE;

        }

    }

    return(ok);
}


typedef struct {
  char *adapter;  /* do not free this, it is freed somewhere else */
  char *bonding;  /* allocated from heap, you must free this */
} bonding_t;

static inline DBusGConnection *get_dbus_gconn(GError **error)
{
  DBusGConnection *conn;

  conn = dbus_g_bus_get(DBUS_BUS_SYSTEM, error);
  return conn;
}



void close_tnc_port()
{

	if (port_data.device_name[0] != 0) {
		//fprintf(stderr, "in close_tnc_port()\n");
		serial_detach();
		//fprintf(stderr, "in close_tnc_port2()\n");

	    int skip_dbus = 0;
	    DBusGConnection *bus = NULL;
	    DBusGProxy *proxy = NULL;
	    GError *error = NULL;

	    bus = get_dbus_gconn(&error);
	    if (!bus) {
	      errno = ECONNREFUSED; /* close enough :) */
	      skip_dbus = 1;
	    }


	    if (!skip_dbus) {
	        /* Disconnect the device */
	        proxy = dbus_g_proxy_new_for_name(bus,
	            BTCOND_DBUS, BTCOND_PATH, BTCOND_INTERFACE);
	        error = NULL;
	        if(!dbus_g_proxy_call(proxy, BTCOND_DISCONNECT, &error,
	              G_TYPE_STRING, port_data.device_name, G_TYPE_INVALID, G_TYPE_INVALID)
	            || error){
//	        	PDEBUG("Cannot send msg (service=%s, object=%s, interface=%s, "
//	        			"method=%s) [%s]\n",
//	                 BTCOND_DBUS,
//	                 BTCOND_PATH,
//	                 BTCOND_INTERFACE,
//	                 BTCOND_DISCONNECT,
//	                 error->message ? error->message : "<no error msg>");
	        }
	        g_object_unref(proxy);
	    }
		//fprintf(stderr, "in close_tnc_port3()\n");
		port_data.device_name[0]=0;
		//fprintf(stderr, "in close_tnc_port4()\n");


	    if (bus) {
	      dbus_g_connection_unref(bus);
	    }
	}
	//fprintf(stderr, "in close_tnc_port5()\n");
}




//***********************************************************
// Serial port INIT
//***********************************************************
void update_aprs_tty_status()
{
	/*
	_aprs_tty_enable = (_aprs_tty_state == RCVR_UP);

	gtk_check_menu_item_set_active(
	    GTK_CHECK_MENU_ITEM(_menu_enable_aprs_tty_item), _aprs_tty_enable);
	    */
}
//***********************************************************
// port_write_string()
//
// port is port# used
// data is the string to write
//***********************************************************
gboolean send_line(gchar* text, gint text_len, TAprsPort port)
{
	if(APRS_PORT_INET == port && !_aprs_enable_inet_tx) return FALSE;
	else if (APRS_PORT_TTY == port && !_aprs_enable_tty_tx) return FALSE;

	if(APRS_PORT_TTY == port)
	{

	}
	gboolean error = FALSE;
	gint i;
    gint write_in_pos_hold = _write_buffer[port].write_in_pos;

    // Lock the mutex
    g_mutex_lock(_write_buffer[port].write_lock);

    for (i = 0; i < text_len && !error; i++) {
    	_write_buffer[port].device_write_buffer[_write_buffer[port].write_in_pos++]
    	                                        = text[i];

        if (_write_buffer[port].write_in_pos >= MAX_DEVICE_BUFFER)
        	_write_buffer[port].write_in_pos = 0;

        if (_write_buffer[port].write_in_pos == _write_buffer[port].write_out_pos) {
            fprintf(stderr,"Port %d Buffer overrun\n",port);

            /* clear this restore original write_in pos and dump this string */
            _write_buffer[port].write_in_pos = write_in_pos_hold;
            _write_buffer[port].errors++;
            error = TRUE;
        }
    }

    g_mutex_unlock(_write_buffer[port].write_lock);

    return error;
}

void port_write_string(gchar *data, gint len, TAprsPort port) {
//    int i,erd,
//	int i;
    int retval;
//    int write_in_pos_hold;

    if (data == NULL)
        return;

    if (data[0] == '\0')
        return;



    if(port == APRS_PORT_TTY)
    {

    	if(g_mutex_trylock (_write_buffer[port].write_lock))
    	{
    		//fprintf(stderr, "TTY Write... ");
		    retval = (int)write(port_data.channel,
		        data,
		        len);
		    //for(i=0;i<len;i++)
		    //	fprintf(stderr,"%c",*(data + i));
		    //fprintf(stderr, "done... ");
		    g_mutex_unlock (_write_buffer[port].write_lock);
		    //fprintf(stderr, "Unlocked\n");
    	}
    	else
    		fprintf(stderr, "Failed to get lock\n");
    }
    else
    {
    	send_line(data, len, port);
    }
}



void tnc_init()
{

	FILE* tncinit;
	char buf[256];
	int len;
	gint aprs_tty_beacon_interval;
	aprs_tty_beacon_interval = convert_str_to_int(_aprs_tty_beacon_interval);


	if(_aprs_tty_enable && _aprs_enable_tty_tx && aprs_tty_beacon_interval>0 && _aprs_igate_method>1)
	{
		if(!(tncinit = g_fopen("/home/user/MyDocs/.maps/aprs/tncinit.conf","r")))
		{
		    hildon_banner_show_information(_window, NULL, "Can not open TNC init file\n/home/user/MyDocs/.maps/aprs/tncinit.conf!");
            fprintf(stderr,"Can not open /home/user/MyDocs/.maps/aprs/tncinit.conf\n");
		}
		else
		{
		    hildon_banner_show_information(_window, NULL, "APRS TNC Initialize...");
			fprintf(stderr,"init TNC!\n");

			while(fgets(buf,256,tncinit)!=NULL)
			{
	 			//gchar *cmd = g_strdup_printf("%s",buf);
			    //hildon_banner_show_information(_window, NULL, cmd);
				len=strlen(buf);
				if(len>=3)
				{
				   *(buf + len - 1)='\r';
				   *(buf + len )='\n';
				   *(buf + len + 1)=0;
				   //for( i =0 ; i<len ; ++i)
				   //         fprintf(stderr,"%02x ",*(buf+i));
					//fprintf(stderr,"\n");
				}

   				port_write_string(buf, strlen(buf), APRS_PORT_TTY);
				fprintf(stderr,"%s",buf);
				sleep(1);
			}
			fprintf(stderr,"init TNC OK!\n");

			fclose(tncinit);
		}
	}
	return;
}

int serial_init () {
    int speed;
    gchar cmd[128];

    fprintf(stderr, "in serial_init\n");

    // clear port_channel
    port_data.channel = -1;

    // clear port active
    port_data.active = DEVICE_NOT_IN_USE;

    // clear port status
    port_data.status = DEVICE_DOWN;
    //set_aprs_tty_conn_state(RCVR_DOWN);

    update_aprs_tty_status();

    //gw-obex.h
    if(_aprs_tnc_method == TNC_CONNECTION_BT)
    {
		sprintf(cmd, "hcitool cc %s", _aprs_tnc_bt_mac );
		fprintf(stderr,cmd);
		fprintf(stderr,"\n");

    	system(cmd);
		sprintf(cmd, "rfcomm bind /dev/rfcomm0 %s", _aprs_tnc_bt_mac );
	   	system(cmd);
		fprintf(stderr,cmd);
		fprintf(stderr,"\n");

    	 strcpy(port_data.device_name,"/dev/rfcomm0");

    	fprintf(stderr, "BT Port: %s\n", port_data.device_name);
    }
    else
    {
    	strcpy(port_data.device_name, _aprs_tty_port);
    }


    // TODO - make these configurable
    port_data.device_type = DEVICE_SERIAL_KISS_TNC;
	port_data.sp = B9600;

	//20110123,add bye nestor,如果配置了GPS输出，则自动将速率减到4800
	if(_aprs_gpsout_enable)
	{
		fprintf(stderr,"COM port speed set to:4800\n");
		port_data.sp = B4800;
	}


    port_data.channel = open(port_data.device_name, O_RDWR|O_NOCTTY);
    if (port_data.channel == -1){

        fprintf(stderr,"Could not open channel on port!\n");

        return (-1);
    }

    // get port attributes for new and old
    if (tcgetattr(port_data.channel, &port_data.t) != 0) {
        fprintf(stderr,"Could not get t port attributes for port!\n");

        // Here we should close the port and remove the lock.
        serial_detach();

        return (-1);
    }

    if (tcgetattr(port_data.channel, &port_data.t_old) != 0) {

        fprintf(stderr,"Could not get t_old port attributes for port!\n");

        // Here we should close the port and remove the lock.
        serial_detach();

        return (-1);
    }

    // set time outs
    port_data.t.c_cc[VMIN] = (cc_t)0;
    port_data.t.c_cc[VTIME] = (cc_t)20;

    // set port flags
    port_data.t.c_iflag &= ~(BRKINT | IGNPAR | PARMRK | INPCK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
    port_data.t.c_iflag = (tcflag_t)(IGNBRK | IGNPAR);

    port_data.t.c_oflag = (0);
    port_data.t.c_lflag = (0);

#ifdef    CBAUD
    speed = (int)(port_data.t.c_cflag & CBAUD);
#else   // CBAUD
    speed = 0;
#endif  // CBAUD

    port_data.t.c_cflag = (tcflag_t)(HUPCL|CLOCAL|CREAD);
    port_data.t.c_cflag &= ~PARENB;
    switch (port_data.style){
        case(0):
            // No parity (8N1)
            port_data.t.c_cflag &= ~CSTOPB;
            port_data.t.c_cflag &= ~CSIZE;
            port_data.t.c_cflag |= CS8;
            break;

        case(1):
            // Even parity (7E1)
            port_data.t.c_cflag &= ~PARODD;
            port_data.t.c_cflag &= ~CSTOPB;
            port_data.t.c_cflag &= ~CSIZE;
            port_data.t.c_cflag |= CS7;
            break;

        case(2):
            // Odd parity (7O1):
            port_data.t.c_cflag |= PARODD;
            port_data.t.c_cflag &= ~CSTOPB;
            port_data.t.c_cflag &= ~CSIZE;
            port_data.t.c_cflag |= CS7;
            break;

        default:
            break;
    }

    port_data.t.c_cflag |= speed;
    // set input and out put speed
    if (cfsetispeed(&port_data.t, port_data.sp) == -1)
    {
    	fprintf(stderr,"Could not set port input speed for port!\n");

        // Here we should close the port and remove the lock.
        serial_detach();

        return (-1);
    }

    if (cfsetospeed(&port_data.t, port_data.sp) == -1) {

        fprintf(stderr,"Could not set port output speed for port!\n");

        // Here we should close the port and remove the lock.
        serial_detach();

        return (-1);
    }

    if (tcflush(port_data.channel, TCIFLUSH) == -1) {

        fprintf(stderr,"Could not flush data for port!\n");

        // Here we should close the port and remove the lock.
        serial_detach();

        return (-1);
    }

    if (tcsetattr(port_data.channel,TCSANOW, &port_data.t) == -1)
    {
        fprintf(stderr,"Could not set port attributes for port!\n");

        // Here we should close the port and remove the lock.
        serial_detach();

        return (-1);
    }

    // clear port active
    port_data.active = DEVICE_IN_USE;

    // clear port status
    port_data.status = DEVICE_UP;
    set_aprs_tty_conn_state(RCVR_UP);

    // Show the latest status in the interface control dialog
    update_aprs_tty_status();

    // Ensure we are in KISS mode
    if(port_data.device_type == DEVICE_SERIAL_KISS_TNC)
    {
    	tnc_init();
    }
    fprintf(stderr,"tnc port opened!\n");
    // return good condition
    return (1);
}

gboolean read_port_data()
{
    unsigned char cin, last;
    gint i;
//    struct timeval tmv;
//    fd_set rd;

    cin = (unsigned char)0;
    last = (unsigned char)0;

	int skip = 0;

    // Handle all EXCEPT AX25_TNC interfaces here
    // Get one character
//fprintf(stderr,"waiting for tty in... ");
	port_data.scan = (int)read(port_data.channel,&cin,1);

	if(port_data.scan == 0) return TRUE;
	else if(port_data.scan < 0) return FALSE;

	//fprintf(stderr,"%02x",cin);


    // Below is code for ALL types of interfaces
    if (port_data.scan > 0 && port_data.status == DEVICE_UP ) {

        if (port_data.device_type != DEVICE_AX25_TNC)
            port_data.bytes_input += port_data.scan;      // Add character to read buffer



        // Handle all EXCEPT AX25_TNC interfaces here
        if (port_data.device_type != DEVICE_AX25_TNC){


            // Do special KISS packet processing here.
            // We save the last character in
            // port_data.channel2, as it is
            // otherwise only used for AX.25 ports.

            if ( (port_data.device_type == DEVICE_SERIAL_KISS_TNC)
                    || (port_data.device_type == DEVICE_SERIAL_MKISS_TNC) ) {


                if (port_data.channel2 == KISS_FESC) { // Frame Escape char
                    if (cin == KISS_TFEND) { // Transposed Frame End char

                        // Save this char for next time
                        // around
                    	port_data.channel2 = cin;

                        cin = KISS_FEND;
                    }
                    else if (cin == KISS_TFESC) { // Transposed Frame Escape char

                        // Save this char for next time
                        // around
                    	port_data.channel2 = cin;

                        cin = KISS_FESC;
                    }
                    else {
                    	port_data.channel2 = cin;
                    }
                }
                else if (port_data.channel2 == KISS_FEND) { // Frame End char
                    // Frame start or frame end.  Drop
                    // the next character which should
                    // either be another frame end or a
                    // type byte.

// Note this "type" byte is where it specifies which KISS interface
// the packet came from.  We may want to use this later for
// multi-drop KISS or other types of KISS protocols.

                    // Save this char for next time
                    // around
                	port_data.channel2 = cin;

                    skip++;
                }
                else if (cin == KISS_FESC) { // Frame Escape char
                	port_data.channel2 = cin;
                    skip++;
                }
                else {
                	port_data.channel2 = cin;
                }
            }   // End of first special KISS processing


            // We shouldn't see any AX.25 flag
            // characters on a KISS interface because
            // they are stripped out by the KISS code.
            // What we should see though are KISS_FEND
            // characters at the beginning of each
            // packet.  These characters are where we
            // should break the data apart in order to
            // send strings to the decode routines.  It
            // may be just fine to still break it on \r
            // or \n chars, as the KISS_FEND should
            // appear immediately afterwards in
            // properly formed packets.


            if ( (!skip)
                    && (cin == (unsigned char)'\r'
                        || cin == (unsigned char)'\n'
                        || port_data.read_in_pos >= (MAX_DEVICE_BUFFER - 1)
                        || ( (cin == KISS_FEND) && (port_data.device_type == DEVICE_SERIAL_KISS_TNC) )
                        || ( (cin == KISS_FEND) && (port_data.device_type == DEVICE_SERIAL_MKISS_TNC) ) )
                   && port_data.data_type == 0) {     // If end-of-line

// End serial/net type data send it to the decoder Put a terminating
// zero at the end of the read-in data

            	port_data.device_read_buffer[port_data.read_in_pos] = (char)0;

                if (port_data.status == DEVICE_UP && port_data.read_in_pos > 0) {
                    int length;

                    // Compute length of string in
                    // circular queue

                    //fprintf(stderr,"%d\t%d\n",port_data.read_in_pos,port_data.read_out_pos);

                    // KISS TNC sends binary data
                    if ( (port_data.device_type == DEVICE_SERIAL_KISS_TNC)
                            || (port_data.device_type == DEVICE_SERIAL_MKISS_TNC) ) {

                        length = port_data.read_in_pos - port_data.read_out_pos;
                        if (length < 0)
                            length = (length + MAX_DEVICE_BUFFER) % MAX_DEVICE_BUFFER;

                        length++;
                    }
                    else {  // ASCII data
                        length = 0;
                    }

                    channel_data(
                        port_data.device_read_buffer,
                        length);   // Length of string
                }

                for (i = 0; i <= port_data.read_in_pos; i++)
                    port_data.device_read_buffer[i] = (char)0;

                port_data.read_in_pos = 0;
            }
            else if (!skip) {

                // Check for binary WX station data
                if (cin == '\0')    // OWW WX daemon sends 0x00's!
                    cin = '\n';

                if (port_data.read_in_pos < (MAX_DEVICE_BUFFER - 1) ) {
                    port_data.device_read_buffer[port_data.read_in_pos] = (char)cin;
                    port_data.read_in_pos++;
                    port_data.device_read_buffer[port_data.read_in_pos] = (char)0;
                }
                else {
                    port_data.read_in_pos = 0;
                }
            }

        }   // End of non-AX.25 interface code block


        return TRUE;
    }
//    else if (port_data.status == DEVICE_UP) {    /* error or close on read */
//    	// cause re-connect
//
//
//    }

    return TRUE;
}
//TNC end


/*aprs add by:nestor*/
static void thread_read_aprs_server()
{
    gchar buf[4096];
	gchar *buf_curr = buf;
	gchar *buf_last = buf + sizeof(buf) - 1;
	GnomeVFSFileSize bytes_read;
	GnomeVFSResult vfs_result;
	gboolean error = FALSE;
	fprintf(stderr, "APRS Server start reading \n");
   _aprs_thread_inet_reading = TRUE;

	while (is_aprs_inet_connected()) {
		gchar *eol;
		error = FALSE;

		vfs_result = gnome_vfs_socket_read(socket_aprs, buf, buf_last
				- buf_curr, &bytes_read, NULL);
		if (vfs_result != GNOME_VFS_OK)
		{
			fprintf(stderr, "Read error: %s\n", gnome_vfs_result_to_string(
					vfs_result));
			error = TRUE;
			break;
		}

		buf_curr += bytes_read;
		*buf_curr = '\0';
		while (is_aprs_inet_connected() && !error && (eol = strchr(buf, '\n')))
		{
			*eol = '\0';
			g_idle_add((GSourceFunc)aprs_parse_server_packet, g_strdup(buf));
			//fprintf(stderr, buf);
			//fprintf(stderr,"\n");
			if (eol >= (buf_curr - 1))
			{
				buf_curr = buf;
				*buf_curr = '\0';
			}
			else
			{
				memmove(buf, eol + 1, buf_curr - eol); /* include terminating 0 */
				buf_curr -= (eol - buf + 1);
			}




            // Send any packets queued
			// try to get lock, otherwise try next time
			if(g_mutex_trylock (_write_buffer[APRS_PORT_INET].write_lock))
			{
	            	// Store the current end pointer as it may change

                gint quantity = 0;
                gchar tmp_write_buffer[MAX_DEVICE_BUFFER];
                while (_write_buffer[APRS_PORT_INET].write_in_pos != _write_buffer[APRS_PORT_INET].write_out_pos) {

                	tmp_write_buffer[quantity] = _write_buffer[APRS_PORT_INET].device_write_buffer[_write_buffer[APRS_PORT_INET].write_out_pos];

                	_write_buffer[APRS_PORT_INET].write_out_pos++;
                    if (_write_buffer[APRS_PORT_INET].write_out_pos >= MAX_DEVICE_BUFFER)
                    	_write_buffer[APRS_PORT_INET].write_out_pos = 0;

                    quantity++;
                }

                if(quantity>0)
                {
                	sleep(2);

                	bytes_read = 0;
					if(GNOME_VFS_OK == gnome_vfs_socket_write( socket_aprs,
							tmp_write_buffer, quantity, &bytes_read, NULL))
					{
						// OK
						//fprintf(stderr, "Send packet success: %s (%u)\n", tmp_write_buffer, quantity);
					}
					else
					{
						// Failed
						fprintf(stderr, "Failed to send packet: %s (%u)\n", tmp_write_buffer, quantity);
						fprintf(stderr, "Failed to send packet!\n");
					}

	                sleep(1);
                }

	            g_mutex_unlock(_write_buffer[APRS_PORT_INET].write_lock);
			}




		}
	}
	_aprs_thread_inet_reading=FALSE;
	fprintf(stderr, "APRS Server end reading \n\n");
	return;
}

/*add end*/




/*add by:nestor*/

gchar *create_aprs_inet_options_string()
{
	gint current_lat = (gint)round(_gps.lat);
	gint current_lon = (gint)round(_gps.lon);
	gint aprs_server_auto_filter_km = 0;
	gchar *filter = NULL;



	//filter = g_strdup_printf("user %s pass %s vers %s v%s filter r/%d/%d/%d \r\n ",
	//		_aprs_mycall, _aprs_inet_server_validation, PACKAGE, VERSION,
	//		current_lat, current_lon, _aprs_server_auto_filter_km );

	if(*_aprs_server_auto_filter>'0' && *_aprs_server_auto_filter<='9')
	{
		aprs_server_auto_filter_km=convert_str_to_int(_aprs_server_auto_filter);
		filter = g_strdup_printf("user %s pass %s vers %s v%s filter r/%d/%d/%d \r\n ",
			_aprs_mycall, _aprs_inet_server_validation, PACKAGE, VERSION,
			current_lat, current_lon, aprs_server_auto_filter_km );
	}
	else
		filter = g_strdup_printf("user %s pass %s vers %s v%s filter %s \r\n ",
			_aprs_mycall, _aprs_inet_server_validation, PACKAGE, VERSION,
			_aprs_server_auto_filter );



	return filter;
}

void connect_aprs_server()
{
    GnomeVFSFileSize bytes_read;
    GnomeVFSResult vfs_result;

    gint tryno;

    if(!is_aprs_inet_connected())
    {
        MACRO_BANNER_SHOW_INFO(_window, _("Connecting to APRS server..."));
        for(tryno = 0; tryno < 10; tryno++)
        {
            GTimeVal timeout = { 1000, 0 };
            gchar *filter = create_aprs_inet_options_string();
            //fprintf(stderr,"aprs try %i connect to server!\n",tryno);
            guint port=convert_str_to_int(_aprs_server_port);

			//fprintf(stderr,"Server:%s\n",filter);
            if(GNOME_VFS_OK != (vfs_result = gnome_vfs_inet_connection_create(
                            &iconn_aprs,
                            _aprs_server,
                            port,
                            NULL))
               || NULL == ( socket_aprs = gnome_vfs_inet_connection_to_socket(iconn_aprs))
               || GNOME_VFS_OK != (vfs_result = gnome_vfs_socket_set_timeout(
            		   socket_aprs, &timeout, NULL))
               || GNOME_VFS_OK != (vfs_result = gnome_vfs_socket_write( socket_aprs,
						filter, strlen(filter), &bytes_read, NULL))
              )
            {
            	g_free(filter);
            	sleep(1);
            }
            else
            {
            	g_free(filter);
                break;
            }
        }
    }

   if(!iconn_aprs)
    {
     	fprintf(stderr,"Can not connecting to APRS server!\n");
		MACRO_BANNER_SHOW_INFO(_window, _("Can not connecting to APRS server!"));
    }
    else
    {
       	fprintf(stderr,"APRS server connected!\n");
		MACRO_BANNER_SHOW_INFO(_window, _("APRS server connected!"));
		g_thread_create((GThreadFunc)thread_read_aprs_server,
					NULL, TRUE, NULL); /* Joinable. */
    }

    return;
}


/*add by:nestor*/


void set_aprs_tty_conn_state(ConnState new_conn_state)
{
     switch(_aprs_tty_state = new_conn_state)
    {
        case RCVR_OFF:
        case RCVR_FIXED:
        case RCVR_UP:
            break;
        case RCVR_DOWN:
            break;

        default: ; /* to quell warning. */
    }

}
void port_read() {
//    unsigned char cin, last;
//    gint i;
    struct timeval tmv;
    fd_set rd;

//    cin = (unsigned char)0;
//    last = (unsigned char)0;
    gboolean success = TRUE;
    GThread *my_thread = g_thread_self();
    _aprs_thread_tnc_reading = TRUE;

    fprintf(stderr, "Enter port_read\n");

    // We stay in this read loop until the port is shut down
    while(port_data.active == DEVICE_IN_USE
    		&& RCVR_UP == _aprs_tty_state && success == TRUE){

        if (port_data.status == DEVICE_UP){

            port_data.read_in_pos = 0;
            port_data.scan = 1;


            while (port_data.scan >= 0
            		&& success == TRUE
            		//&& RCVR_UP == _aprs_tty_state
                    && (port_data.read_in_pos < (MAX_DEVICE_BUFFER - 1) )
                    && (port_data.status == DEVICE_UP)
                    && (_aprs_tty_thread == my_thread)
                )
            {

                success = read_port_data();
            }

        }
        if (port_data.active == DEVICE_IN_USE)  {



        	// We need to delay here so that the thread doesn't use
            // high amounts of CPU doing nothing.

// This select that waits on data and a timeout, so that if data
// doesn't come in within a certain period of time, we wake up to
// check whether the socket has gone down.  Else, we go back into
// the select to wait for more data or a timeout.  FreeBSD has a
// problem if this is less than 1ms.  Linux works ok down to 100us.
// We don't need it anywhere near that short though.  We just need
// to check whether the main thread has requested the interface be
// closed, and so need to have this short enough to have reasonable
// response time to the user.

//sched_yield();  // Yield to other threads

            // Set up the select to block until data ready or 100ms
            // timeout, whichever occurs first.
            FD_ZERO(&rd);
            FD_SET(port_data.channel, &rd);
            tmv.tv_sec = 0;
            tmv.tv_usec = 100000;    // 100 ms
            (void)select(0,&rd,NULL,NULL,&tmv);
        }
    }

    _aprs_thread_tnc_reading = FALSE;
    fprintf(stderr, "End of port_read\n");
}


static void thread_read_tty()
{
	//GThread *my_thread = g_thread_self();

	//if(_aprs_tty_thread == my_thread )
	{
		if( serial_init() >= 0 )
		{
			port_read();

			//if(_aprs_tty_thread == my_thread)
			{
	            fprintf(stderr,"Read TNC Port error.");

			}
		}
		else
		{
			fprintf(stderr, "Failed to init serial port\n");

 		}

		close_tnc_port();
	}
}
gboolean aprs_tty_connect()
{
    if(_aprs_tty_enable && _aprs_tty_state == RCVR_OFF)
    {
        set_aprs_tty_conn_state(RCVR_DOWN);

        // Lock/Unlock the mutex to ensure that the thread doesn't
        // start until _gps_thread is set.
        fprintf(stderr,"aprs_tty_connect in\n");

        _aprs_tty_thread = g_thread_create((GThreadFunc)thread_read_tty,
                NULL, TRUE, NULL); // Joinable.

    }
    return FALSE;

}

void aprs_tty_disconnect()
{

    printf("%s()\n", __PRETTY_FUNCTION__);

    //GThread *my_thread = g_thread_self();

    //if(my_thread == _aprs_tty_thread)
    {
        close_tnc_port();
    }

    _aprs_tty_thread = NULL;

    set_aprs_tty_conn_state(RCVR_OFF);

}












// This routine changes callsign chars to proper uppercase chars or
// numerals, fixes the callsign to six bytes, shifts the letters left by
// one bit, and puts the SSID number into the proper bits in the seventh
// byte.  The callsign as processed is ready for inclusion in an
// AX.25 header.
//
void fix_up_callsign(gchar *data, int data_size) {
    unsigned char new_call[8] = "       ";  // Start with seven spaces
    int ssid = 0;
    int i;
    int j = 0;
    int digipeated_flag = 0;


    // Check whether we've digipeated through this callsign yet.
    if (strstr((const char *)data,"*") != 0) {
         digipeated_flag++;
    }

    // Change callsign to upper-case and pad out to six places with
    // space characters.
    for (i = 0; i < (int)strlen((const char *)data); i++) {
        toupper(data[i]);

        if (data[i] == '-') {   // Stop at '-'
            break;
        }
        else if (data[i] == '*') {
        }
        else {
            new_call[j++] = data[i];
        }
    }
    new_call[7] = '\0';

    //fprintf(stderr,"new_call:(%s)\n",new_call);

    // Handle SSID.  'i' should now be pointing at a dash or at the
    // terminating zero character.
    if ( (i < (int)strlen((const char *)data)) && (data[i++] == '-') ) {   // We might have an SSID
        if (data[i] != '\0')
            ssid = atoi((const char *)&data[i]);
//            ssid = data[i++] - 0x30;    // Convert from ascii to int
//        if (data[i] != '\0')
//            ssid = (ssid * 10) + (data[i] - 0x30);
    }

//fprintf(stderr,"SSID:%d\t",ssid);

    if (ssid >= 0 && ssid <= 15) {
        new_call[6] = ssid | 0x30;  // Set 2 reserved bits
    }
    else {  // Whacko SSID.  Set it to zero
        new_call[6] = 0x30;     // Set 2 reserved bits
    }

    if (digipeated_flag) {
        new_call[6] = new_call[6] | 0x40; // Set the 'H' bit
    }

    // Shift each byte one bit to the left
    for (i = 0; i < 7; i++) {
        new_call[i] = new_call[i] << 1;
        new_call[i] = new_call[i] & 0xfe;
    }

//fprintf(stderr,"Last:%0x\n",new_call[6]);

    // Write over the top of the input string with the newly
    // formatted callsign
    snprintf((char *)data,
        data_size,
        "%s",
        new_call);
}


// Create an AX25 frame and then turn it into a KISS packet.  Dump
// it into the transmit queue.
//
void send_ax25_frame(TAprsPort port, gchar *source, gchar *destination, gchar *path, gchar *data) {
    gchar temp_source[15];
    gchar temp_dest[15];
    gchar temp[15];
    gchar control[2], pid[2];
    gchar transmit_txt[MAX_LINE_SIZE*2];
    gchar transmit_txt2[MAX_LINE_SIZE*2];
    gchar c;
    int i, j;
    int erd;


//fprintf(stderr,"KISS String:%s>%s,%s:%s\n",source,destination,path,data);

    // Check whether transmits are disabled globally
//    if (transmit_disable) {
//        return;
//    }

    // Check whether transmit has been enabled for this interface.
    // If not, get out while the gettin's good.
//    if (devices[port].transmit_data != 1) {
//        return;
//    }
	//fprintf(stderr,"send_ax25_frame1(source):%s\n",source);

	//				                   BD2STH-9       APRS                WIDE1-1,WIDE2-1    =3851.30N\12131.51E>n810
	//ssend_ax25_frame(TAprsPort port, gchar *source, gchar *destination, gchar *path,       gchar *data)
    transmit_txt[0] = '\0';

    // Format the destination callsign
    snprintf((char *)temp_dest,
        sizeof(temp_dest),
        "%s",
        destination);
    fix_up_callsign(temp_dest, sizeof(temp_dest));

    snprintf((char *)transmit_txt,
        sizeof(transmit_txt),
        "%s",
        temp_dest);

    // Format the source callsign
    snprintf((char *)temp_source,
        sizeof(temp_source),
        "%s",
        source);
	//fprintf(stderr,"send_ax25_frame2(temp_source):%s\n",temp_source);
    fix_up_callsign(temp_source, sizeof(temp_source));

    strncat((char *)transmit_txt,
        (char *)temp_source,
        sizeof(transmit_txt) - strlen((char *)transmit_txt));

    // Break up the path into individual callsigns and send them one
    // by one to fix_up_callsign().  If we get passed an empty path,
    // we merely skip this section and no path gets added to
    // "transmit_txt".
    j = 0;
    temp[0] = '\0'; // Start with empty path
    if ( (path != NULL) && (strlen(path) != 0) ) {
        while (path[j] != '\0') {
            i = 0;
            while ( (path[j] != ',') && (path[j] != '\0') ) {
                temp[i++] = path[j++];
            }
            temp[i] = '\0';

            if (path[j] == ',') {   // Skip over comma
                j++;
            }

            fix_up_callsign(temp, sizeof(temp));
            strncat((char *)transmit_txt,
                (char *)temp,
                sizeof(transmit_txt) - strlen((char *)transmit_txt));
        }
    }

    // Set the end-of-address bit on the last callsign in the
    // address field
    transmit_txt[strlen((const char *)transmit_txt) - 1] |= 0x01;

    // Add the Control byte
    control[0] = 0x03;
    control[1] = '\0';
    strncat((char *)transmit_txt,
        (char *)control,
        sizeof(transmit_txt) - strlen((char *)transmit_txt));

    // Add the PID byte
    pid[0] = 0xf0;
    pid[1] = '\0';
    strncat((char *)transmit_txt,
        (char *)pid,
        sizeof(transmit_txt) - strlen((char *)transmit_txt));

    // Append the information chars
    strncat((char *)transmit_txt,
        data,
        sizeof(transmit_txt) - strlen((char *)transmit_txt));

    //fprintf(stderr,"%s\n",transmit_txt);

    // Add the KISS framing characters and do the proper escapes.
    j = 0;
    transmit_txt2[j++] = KISS_FEND;

    // Note:  This byte is where different interfaces would be
    // specified:
    transmit_txt2[j++] = 0x00;

    for (i = 0; i < (int)strlen((const char *)transmit_txt); i++) {
        c = transmit_txt[i];
        if (c == KISS_FEND) {
            transmit_txt2[j++] = KISS_FESC;
            transmit_txt2[j++] = KISS_TFEND;
        }
        else if (c == KISS_FESC) {
            transmit_txt2[j++] = KISS_FESC;
            transmit_txt2[j++] = KISS_TFESC;
        }
        else {
            transmit_txt2[j++] = c;
        }
    }
    transmit_txt2[j++] = KISS_FEND;

    // Terminate the string, but don't increment the 'j' counter.
    // We don't want to send the NULL byte out the KISS interface,
    // just make sure the string is terminated in all cases.
    //
    transmit_txt2[j] = '\0';

//-------------------------------------------------------------------
// Had to snag code from port_write_string() below because our string
// needs to have 0x00 chars inside it.  port_write_string() can't
// handle that case.  It's a good thing the transmit queue stuff
// could handle it.
//-------------------------------------------------------------------

    erd = 0;




	//fprintf(stderr,"send_ax25_frame3\n");
	port_write_string(
			transmit_txt2,
			j/*length*/,
		APRS_PORT_TTY);
	//fprintf(stderr,"send_ax25_frame4\n");



/*
    g_mutex_lock (_write_buffer[port].write_lock);
    {

	    write_in_pos_hold = _write_buffer[port].write_in_pos;

	    for (i = 0; i < j && !erd; i++) {
	    	_write_buffer[port].device_write_buffer[_write_buffer[port].write_in_pos++] = transmit_txt2[i];
	        if (_write_buffer[port].write_in_pos >= MAX_DEVICE_BUFFER)
	        	_write_buffer[port].write_in_pos = 0;

	        if (_write_buffer[port].write_in_pos == _write_buffer[port].write_out_pos) {

	            // clear this restore original write_in pos and dump this string
	        	_write_buffer[port].write_in_pos = write_in_pos_hold;
	        	_write_buffer[port].errors++;
	            erd = 1;
	        }
	    }

	    g_mutex_unlock (_write_buffer[port].write_lock);
    }
*/
	//fprintf(stderr,"Send Over:%s %s %s %s\n",source,destination,path,data);
}




void create_output_pos_packet(TAprsPort port, gchar **packet, int *length)
{

//	gchar encodedPos[MAX_LINE_SIZE];

	//if(_aprs_transmit_compressed_posit)
	//{
	//	// TODO
	//}
	//else
	{
		//!5122.09N/00008.42W&APRS4R IGATE RUNNING ON NSLU2

		// For now just use a simple packet

		gchar slat[10];
		gchar slon[10];

		gdouble  gps_lon=0,gps_lat=0;
		//add by:nestor 纠正 N810 发送伪坐标的问题(在offset=1时有效)
		//add by:nestor:考虑到在aprs.fi上展现的问题，对offset进一步进行处理
		//offset>0时表明有需要纠偏，当offset>2时则需要在发射时不纠偏
		//通过电台发射或纠偏模式1一律采用真实坐标
		if(_aprs_igate_method==1 || port == APRS_PORT_TTY)
		{
			gps_lat = _gps.lat - _offset_lat;
			gps_lon = _gps.lon - _offset_lon ;
		}
		else
		{
			gps_lat = _gps.lat;
			gps_lon = _gps.lon;
		}
		//add:end

		gdouble pos = (gps_lat > 0 ? gps_lat : 0-gps_lat) ;



		gdouble min = (pos - (int)pos)*60.0;
		sprintf(slat, "%02d%02d.%02.0f", (int)pos, (int)min,
		                    ((min - (int)min)*100.0) );

		pos = (gps_lon > 0 ? gps_lon : 0-gps_lon);

		min = (pos - (int)pos)*60.0;
		sprintf(slon, "%03d%02d.%02.0f", (int)pos, (int)min,
				                    ((min - (int)min)*100.0) );

		//fprintf(stderr, "create_output_pos_packet 1\n" );
		if(port == APRS_PORT_TTY)
			*packet = g_strdup_printf(
				"%c%s%c%c%s%c%c%s%c",
				'=',
				slat,
				(gps_lat > 0 ? 'N' : 'S'),
				_aprs_beacon_group,
				slon,
				(gps_lon > 0 ? 'E' : 'W'),
				_aprs_beacon_symbol,
				(port == APRS_PORT_INET ? _aprs_inet_beacon_comment : _aprs_beacon_comment),
				(char)0
				);
		else
			*packet = g_strdup_printf(
				"%c%s%c%c%s%c%c*%s%c",
				'=',
				slat,
				(gps_lat > 0 ? 'N' : 'S'),
				_aprs_beacon_group,
				slon,
				(gps_lon > 0 ? 'E' : 'W'),
				_aprs_beacon_symbol,
				(port == APRS_PORT_INET ? _aprs_inet_beacon_comment : _aprs_beacon_comment),
				(char)0
				);

		//fprintf(stderr,"packet:%s\n",*packet);
		//add by:nestor 2010/05/25
		if(port == APRS_PORT_INET)
		{
			MACRO_BANNER_SHOW_INFO(_window, _("APRS SENT (Internet)"));
		}
		else
		{
			MACRO_BANNER_SHOW_INFO(_window, _("APRS SEND ( TNC )"));
		}

		//fprintf(stderr, "create_output_pos_packet 2\n" );
		//add:enr
	}

	*length = strlen(*packet);
}


void send_packet(TAprsPort port, gchar* from_call,gchar* to_call, gchar* path, gchar* packet, gint packet_length)
{
	static unsigned long int tnc_packet_count=0;
	static unsigned long int inet_packet_count=0;

	if(port == APRS_PORT_INET
			|| !(port_data.device_type == DEVICE_SERIAL_KISS_TNC
	        || port_data.device_type == DEVICE_SERIAL_MKISS_TNC) )
	{

		/*这是原来的发送程序，因为支持iGate必须要改动了
		gchar *packet_header
			=  g_strdup_printf(
	        "%s>%s,%s:",
	        _aprs_mycall,
	        to_call,
	        path);

		gchar *full_packet = g_strdup_printf("%s%s\r\n", packet_header, packet);
		fprintf(stderr,"(INET)packet:%s\n",full_packet);
		*/

		gchar *packet_header
			=  g_strdup_printf(
	        "%s>%s,%s:",
	        from_call,
	        to_call,
	        path);
		gchar *full_packet = g_strdup_printf("%s%s\r\n", packet_header, packet);
		send_line(full_packet, strlen(packet_header)+packet_length+2,  port);
		fprintf(stderr,"(INET Send %ld):%s\n",inet_packet_count,full_packet);
		inet_packet_count ++;

	}
	else
	{
		/*这是原来的发送程序，因为支持iGate必须要改动了

		send_ax25_frame(port, _aprs_mycall, to_call, path, packet);
		*/
		//由于原程序有错误，呼号过长的对不起要必须给截位，去了得了
		/*
		int len;
		len=strlen(from_call);
		if (len>=8)
		{
			if(*(from_call+6)=='-')
				*(from_call+6)='\0';
			else
				*(from_call+7)='\0';
		}
		*/
		//(TNC Send(1)BD2STH-9 APRS WIDE1-1,WIDE2-1 =3851.30N\12131.51E>n810
		if(_aprs_igate_method>1)		//只有处在iGate的发射模式下才使用TNC发射,也因为iGate>1 TNC才被初始化
		{

			//				          BD2STH-9    APRS            WIDE1-1,WIDE2-1 =3851.30N\12131.51E>n810
			send_ax25_frame(port,     from_call,  to_call,        path,           packet);
			fprintf(stderr,"(TNC Send %ld)%s %s %s %s\n",tnc_packet_count,from_call,to_call,path,packet);
			tnc_packet_count++;
			//fprintf(stderr,"TNC had sent:%i packets!\n",tnc_packet_count);
			//if(tnc_packet_count>48)
			//{
				//每转发100个数据包，休息一下
				//tnc_init();
				//tnc_packet_count=0;
			//}
		}

	}

}

void output_my_msg_data(TAprsPort port,gchar *to_call,gchar *msg_data) {


	int length = 0;
	gchar buf[100];

	gchar slat[10],slon[10];
	gdouble pos = (_gps.lat > 0 ? _gps.lat : 0-_gps.lat) ;
	gdouble min = (pos - (int)pos)*60.0;
	sprintf(slat, "%02d%02d.%02.0f", (int)pos, (int)min,
						((min - (int)min)*100.0) );

	pos = (_gps.lon > 0 ? _gps.lon : 0 - _gps.lon);

	min = (pos - (int)pos)*60.0;
	sprintf(slon, "%03d%02d.%02.0f", (int)pos, (int)min,
								((min - (int)min)*100.0) );
	//create_output_msg_packet
	{
	    memset(buf, 32, sizeof(buf));
	    *buf = ':';
	    memcpy(buf+1,to_call,strlen(to_call));
	    *(buf+10) = ':';
	    strcpy(buf+11,msg_data);
	    //strcat(buf,"\r\n");
	    //fprintf(stderr,"MSG:%s,%i\n",buf,strlen(buf));
	    //send_line(buf,strlen(buf) + 2,APRS_PORT_INET);
	    length = strlen(buf);
	}

	send_packet(port, _aprs_mycall,VERSIONFRM, _aprs_unproto_path, buf, length);
	if(port==APRS_PORT_INET)
	{
		MACRO_BANNER_SHOW_INFO(_window, _("APRS MSG SENT (Internet)"));
	}
	else
	{
		MACRO_BANNER_SHOW_INFO(_window, _("APRS MSG SENT (TNC)"));
	}



}

void output_my_aprs_data(TAprsPort port) {


	gchar *packet;
	int length = 0;

    static gchar prev_slat[10],prev_slon[10];
	gchar slat[10],slon[10];
	gdouble pos = (_gps.lat > 0 ? _gps.lat : 0-_gps.lat) ;
	gdouble min = (pos - (int)pos)*60.0;
	sprintf(slat, "%02d%02d.%02.0f", (int)pos, (int)min,
						((min - (int)min)*100.0) );

	pos = (_gps.lon > 0 ? _gps.lon : 0 - _gps.lon);

	min = (pos - (int)pos)*60.0;
	sprintf(slon, "%03d%02d.%02.0f", (int)pos, (int)min,
								((min - (int)min)*100.0) );
	if(strcmp(prev_slat,slat) || strcmp(prev_slat,slon))
	{
		create_output_pos_packet(port, &packet, &length);
		send_packet(port, _aprs_mycall,VERSIONFRM, _aprs_unproto_path, packet, length);
		strcpy(prev_slat,slat);
		strcpy(prev_slon,slon);

	}
	else
	{
		MACRO_BANNER_SHOW_INFO(_window, _("APRS NOT SENT\nDuplicate position"));
	}
	if(packet != NULL) g_free(packet);
}

//新增加的iGate函数，用来向互联网转发数据
//add by:nestor 2011/01/25
void output_igate_aprs_data(TAprsPort port,gchar* from_call, gchar* path, gchar* packet, gint packet_length) {


	int is_trans=0;
	char* path_pos = 0;
	int hops = 0;
	int dirty = 0;
	char new_path[256];
	char path_buf[128];
	if(path !=NULL )
	{
		//看看最后一位是否为-，如果有，则是坏路径
		if(*(path + strlen(path) - 1)!='-' && strlen(path) <120)
		{

			//处理指定路径(呼号)
			path_pos = strstr(path,_aprs_mycall);
			if( path_pos!=NULL )
			{
				int len=0;
				len = strlen(_aprs_mycall);
				if(*(path_pos + len)!='*')
				{
					strncpy(path_buf,path,(int)(path_pos - path) + len);
					path_buf[(int)(path_pos - path) + len] = '\0';
					if(*(path_pos + len ) != '\0')
						sprintf(new_path,"%s*%s",path_buf,path_pos + len );
					else
						sprintf(new_path,"%s*",path_buf);
					is_trans=1;
				}
				else
					dirty = 1;
			}

			//处理第一路径
			//先找到WIDE1
			if(is_trans==0 && dirty==0) //呼号路径没处理
			{
				path_pos=strstr(path,"WIDE1-");
				if(path_pos==NULL)
					path_pos=strstr(path,"wide1-");
				if(path_pos)
				{
					hops=atoi(path_pos+6);
					if (hops>0)
					{
						if(hops==1)
						{
							//找到WIDE1后面数
							strncpy(path_buf,path,(int)(path_pos - path) + 5);
							path_buf[(int)(path_pos - path) + 5] = '\0';
							if(*(path_pos + 7) != '\0')
								sprintf(new_path,"%s*%s,%s*",path_buf,path_pos + 7,_aprs_mycall);
							else
								sprintf(new_path,"%s*,%s*",path_buf,_aprs_mycall);
						}
						else
						{
							//找到WIDE1后面数
							strncpy(path_buf,path,(int)(path_pos - path) + 6);
							path_buf[(int)(path_pos - path) + 6] = '\0';
							if(*(path_pos + 7) != '\0')
								sprintf(new_path,"%s%i%s,%s*",path_buf,hops - 1 ,path_pos + 7,_aprs_mycall);
							else
								sprintf(new_path,"%s%i,%s*",path_buf,hops - 1 ,_aprs_mycall);

						}
						is_trans=1;
					}
				}
			}
			if(is_trans==0 && dirty==0) //第一路径没处理
			{
				//处理第二路径
				path_pos=strstr(path,"WIDE2-");
				if(path_pos==NULL)
					path_pos=strstr(path,"wide2-");
				if(path_pos)
				{
					hops=atoi(path_pos+6);
					if (hops>0)
					{
						if(hops==1)
						{
							//找到WIDE1后面数
							strncpy(path_buf,path,(int)(path_pos - path) + 5);
							path_buf[(int)(path_pos - path) + 5] = '\0';
							if(*(path_pos + 7) != '\0')
								sprintf(new_path,"%s*%s,%s*",path_buf,path_pos + 7,_aprs_mycall);
							else
								sprintf(new_path,"%s*,%s*",path_buf,_aprs_mycall);
						}
						else
						{
							//找到WIDE1后面数
							strncpy(path_buf,path,(int)(path_pos - path) + 6);
							path_buf[(int)(path_pos - path) + 6] = '\0';
							if(*(path_pos + 7) != '\0')
								sprintf(new_path,"%s%i%s,%s*",path_buf,hops - 1 ,path_pos + 7,_aprs_mycall);
							else
								sprintf(new_path,"%s%i,%s*",path_buf,hops - 1 ,_aprs_mycall);

						}
						is_trans=1;
					}
				}//第二路径用完，丢弃这个包

			}
			if(is_trans==0  && dirty==0 && strlen(path) <120 && strstr(path,"WIDE") == NULL && (strstr(path,"TCPIP*") != NULL || strstr(path,"tcpip*") != NULL)) //没有WIDE
			{
				//如果是TCPIP的包，且是发往TTY的，可以转发一次

				sprintf(new_path,"%s,WIDE1*,WIDE2*,%s*",path,_aprs_mycall);
				is_trans=1;
			}

		}

	}
	else
	{

		//没有路径的调用来自于输出waypoints的电台,不允许再次被转发
		sprintf(new_path,"WIDE1*,WIDE2*,%s*",_aprs_mycall);
		is_trans=1;
	}


	if(is_trans == 1)
		send_packet(port, from_call,VERSIONFRM, new_path, packet, packet_length);
		//fprintf(stderr, "output_my_aprs_data 3\n" );

	if(packet != NULL) g_free(packet);
}




gboolean aprs_send_beacon(TAprsPort port)
{
	if(_aprs_enable)
	{
		//fprintf(stderr, "aprs_send_beacon 1\n" );
		output_my_aprs_data(port);
		//fprintf(stderr, "aprs_send_beacon 2\n" );
	}

	return TRUE;
}


gboolean timer_callback_aprs_inet (gpointer data) {

	gint aprs_inet_beacon_interval;
	aprs_inet_beacon_interval = convert_str_to_int(_aprs_inet_beacon_interval);
	if(!_aprs_thread_inet_reading)
	{
		aprs_inet_init();
	}

	if(_aprs_inet_enable && _aprs_enable_inet_tx && aprs_inet_beacon_interval>0)
	{
		aprs_send_beacon(APRS_PORT_INET);
		return TRUE; // Continue timer
	}

	return FALSE; // Stop timer
}



gboolean timer_callback_aprs_tty (gpointer data) {

	gint aprs_tty_beacon_interval;
	aprs_tty_beacon_interval = convert_str_to_int(_aprs_tty_beacon_interval);
	if(!_aprs_thread_tnc_reading)
	{
		aprs_tnc_init();
	}

	if(_aprs_tty_enable && _aprs_enable_tty_tx && aprs_tty_beacon_interval>0)
	{
		//fprintf(stderr, "Sending beacon for TNC...\n");
		aprs_send_beacon(APRS_PORT_TTY);
		return TRUE; // Continue timer
	}

	return FALSE; // Stop timer
}


void aprs_inet_timer_init()
{
	gint aprs_inet_beacon_interval;
	aprs_inet_beacon_interval = convert_str_to_int(_aprs_inet_beacon_interval);

	// disable timer if exists
	if(_aprs_inet_beacon_timer>0) g_source_remove(_aprs_inet_beacon_timer);

	if(_aprs_enable)
	{
		if(_aprs_inet_enable && _aprs_enable_inet_tx && aprs_inet_beacon_interval>0)
			_aprs_inet_beacon_timer = g_timeout_add(aprs_inet_beacon_interval*1000 , timer_callback_aprs_inet, NULL);

	}
}
void aprs_tnc_timer_init()
{
	gint aprs_tty_beacon_interval;
	aprs_tty_beacon_interval = convert_str_to_int(_aprs_tty_beacon_interval);

	// disable timer if exists
	if(_aprs_tty_beacon_timer>0) g_source_remove(_aprs_tty_beacon_timer);

	if(_aprs_enable)
	{
		if(_aprs_tty_enable && _aprs_enable_tty_tx && _aprs_tty_beacon_interval>0)
			_aprs_tty_beacon_timer = g_timeout_add(aprs_tty_beacon_interval*1000 , timer_callback_aprs_tty, NULL);
	}
}
/*add end*/




/***********************************************************************/
/****************************APRS Mesesage START************************/
/***********************************************************************/
#define MAX_MESSAGE_ORDER    10
#define RECORD_ACTIVE    'A'
#define RECORD_NOTACTIVE 'N'
#define MESSAGE_MESSAGE  'M'
#define MESSAGE_BULLETIN 'B'
#define MESSAGE_NWS      'W'
#define VIA_TNC         'Y'
#define NOT_VIA_TNC     'N'
#define MESSAGE_REMOVE_CYCLE 600    /* check message remove in seconds (every 10 minutes) */
time_t   _aprs_sec_remove;              /* Station removed after */
#define MSG_INCREMENT 200

// Used for messages and bulletins
typedef struct {
    char active;
    TAprsPort port;
    char type;
    char heard_via_tnc;
    time_t sec_heard;
    time_t last_ack_sent;
    char packet_time[MAX_TIME];
    char call_sign[MAX_CALLSIGN+1];
    char from_call_sign[MAX_CALLSIGN+1];
    char message_line[MAX_MESSAGE_LENGTH+1];
    char seq[MAX_MESSAGE_ORDER+1];
    char acked;
    char position_known;
    time_t interval;
    int tries;
} Message;



// Struct used to create linked list of most recent ack's
typedef struct _ack_record {
    char callsign[MAX_CALLSIGN+1];
    char ack[5+1];
    struct _ack_record *next;
} ack_record;

long *msg_index;
long msg_index_end;
static long msg_index_max;

int log_wx_alert_data = 0;

Message *msg_data; // Array containing all messages,
                          // including ones we've transmitted (via
                          // loopback in the code)

time_t last_message_update = 0;
ack_record *ack_list_head = NULL;  // Head of linked list storing most recent ack's
int satellite_ack_mode;

int  new_message_data;
time_t last_message_remove;     // last time we did a check for message removing

// How often update_messages() will run, in seconds.
// This is necessary because routines like UpdateTime()
// call update_messages() VERY OFTEN.
//
// Actually, we just changed the code around so that we only call
// update_messages() with the force option, and only when we receive a
// message.  message_update_delay is no longer used, and we don't call
// update_messages() from UpdateTime() anymore.
static int message_update_delay = 300;




char *remove_trailing_spaces(char *data);
void update_messages(int force);




void clear_acked_message(char *from, char *to, char *seq) {
	// TODO - replace stub
}

int check_popup_window(char *from_call_sign, int group) {
	// TODO - replace stub
	return 1;
}

void bulletin_data_add(char *call_sign, char *from_call, char *data, char *seq, char type, TAprsPort port)
{
	// TODO - replace stub
}

int look_for_open_group_data(char *to) {
	// TODO - replace stub
	return 0;
}

void get_send_message_path(char *callsign, char *path, int path_size)
{
	// TODO - replace stub
}

void transmit_message_data_delayed(char *to, char *message,
                                   char *path, time_t when) {
	// TODO - replace stub
}

int process_directed_query(char *call,char *path,char *message,TAprsPort port) {
	// TODO - replace stub
	return 0;
}

void transmit_message_data(char *to, char *message, char *path)
{
	// TODO - replace stub
}

void popup_message(char *banner, char *message)
{

}

// Saves latest ack in a linked list.  We need this value in order
// to use Reply/Ack protocol when sending out messages.
void store_most_recent_ack(char *callsign, char *ack) {
    ack_record *p;
    int done = 0;
    char call[MAX_CALLSIGN+1];
    char new_ack[5+1];

    snprintf(call,
        sizeof(call),
        "%s",
        callsign);
    remove_trailing_spaces(call);

    // Get a copy of "ack".  We might need to change it.
    snprintf(new_ack,
        sizeof(new_ack),
        "%s",
        ack);

    // If it's more than 2 characters long, we can't use it for
    // Reply/Ack protocol as there's only space enough for two.
    // In this case we need to make sure that we blank out any
    // former ack that was 1 or 2 characters, so that communications
    // doesn't stop.
    if ( strlen(new_ack) > 2 ) {
        // It's too long, blank it out so that gets saved as "",
        // which will overwrite any previously saved ack's that were
        // short enough to use.
        new_ack[0] = '\0';
    }

    // Search for matching callsign through linked list
    p = ack_list_head;
    while ( !done && (p != NULL) ) {
        if (strcasecmp(call,p->callsign) == 0) {
            done++;
        }
        else {
            p = p->next;
        }
    }

    if (done) { // Found it.  Update the ack field.
        //fprintf(stderr,"Found callsign %s on recent ack list, Old:%s, New:%s\n",call,p->ack,new_ack);
        snprintf(p->ack,sizeof(p->ack),"%s",new_ack);
    }
    else {  // Not found.  Add a new record to the beginning of the
            // list.
        //fprintf(stderr,"New callsign %s, adding to list.  Ack: %s\n",call,new_ack);
        p = (ack_record *)malloc(sizeof(ack_record));
        CHECKMALLOC(p);

        snprintf(p->callsign,sizeof(p->callsign),"%s",call);
        snprintf(p->ack,sizeof(p->ack),"%s",new_ack);
        p->next = ack_list_head;
        ack_list_head = p;
    }
}





// Gets latest ack by callsign
char *get_most_recent_ack(char *callsign) {
    ack_record *p;
    int done = 0;
    char call[MAX_CALLSIGN+1];

    snprintf(call,
        sizeof(call),
        "%s",
        callsign);
    remove_trailing_spaces(call);

    // Search for matching callsign through linked list
    p = ack_list_head;
    while ( !done && (p != NULL) ) {
        if (strcasecmp(call,p->callsign) == 0) {
            done++;
        }
        else {
            p = p->next;
        }
    }

    if (done) { // Found it.  Return pointer to ack string.
        //fprintf(stderr,"Found callsign %s on linked list, returning ack: %s\n",call,p->ack);
        return(&p->ack[0]);
    }
    else {
        //fprintf(stderr,"Callsign %s not found\n",call);
        return(NULL);
    }
}





void init_message_data(void) {  // called at start of main

    new_message_data = 0;
    last_message_remove = sec_now();
}






void msg_clear_data(Message *clear) {
    int size;
    int i;
    unsigned char *data_ptr;

    data_ptr = (unsigned char *)clear;
    size=sizeof(Message);
    for(i=0;i<size;i++)
        *data_ptr++ = 0;
}



#ifdef MSG_DEBUG

void msg_copy_data(Message *to, Message *from) {
    int size;
    int i;
    unsigned char *data_ptr;
    unsigned char *data_ptr_from;

    data_ptr = (unsigned char *)to;
    data_ptr_from = (unsigned char *)from;
    size=sizeof(Message);
    for(i=0;i<size;i++)
        *data_ptr++ = *data_ptr_from++;
}
#endif /* MSG_DEBUG */





// Returns 1 if it's time to update the messages again
int message_update_time (void) {
    if ( sec_now() > (last_message_update + message_update_delay) )
        return(1);
    else
        return(0);
}





int msg_comp_active(const void *a, const void *b) {
    char temp_a[MAX_CALLSIGN+MAX_CALLSIGN+MAX_MESSAGE_ORDER+2];
    char temp_b[MAX_CALLSIGN+MAX_CALLSIGN+MAX_MESSAGE_ORDER+2];

    snprintf(temp_a, sizeof(temp_a), "%c%s%s%s",
            ((Message*)a)->active, ((Message*)a)->call_sign,
            ((Message*)a)->from_call_sign,
            ((Message*)a)->seq);
    snprintf(temp_b, sizeof(temp_b), "%c%s%s%s",
            ((Message*)b)->active, ((Message*)b)->call_sign,
            ((Message*)b)->from_call_sign,
            ((Message*)b)->seq);

    return(strcmp(temp_a, temp_b));
}





int msg_comp_data(const void *a, const void *b) {
    char temp_a[MAX_CALLSIGN+MAX_CALLSIGN+MAX_MESSAGE_ORDER+1];
    char temp_b[MAX_CALLSIGN+MAX_CALLSIGN+MAX_MESSAGE_ORDER+1];

    snprintf(temp_a, sizeof(temp_a), "%s%s%s",
            msg_data[*(long*)a].call_sign, msg_data[*(long *)a].from_call_sign,
            msg_data[*(long *)a].seq);
    snprintf(temp_b, sizeof(temp_b), "%s%s%s", msg_data[*(long*)b].call_sign,
            msg_data[*(long *)b].from_call_sign, msg_data[*(long *)b].seq);

    return(strcmp(temp_a, temp_b));
}





void msg_input_database(Message *m_fill) {
    void *m_ptr;
    long i;

//    fprintf(stderr, "DEBUG: Message: %s  %s\n", m_fill->call_sign, m_fill->message_line);

    if (msg_index_end == msg_index_max) {
        for (i = 0; i < msg_index_end; i++) {

            // Check for a record that is marked RECORD_NOTACTIVE.
            // If found, use that record instead of malloc'ing a new
            // one.
            if (msg_data[msg_index[i]].active == RECORD_NOTACTIVE) {

                // Found an unused record.  Fill it in.
                memcpy(&msg_data[msg_index[i]], m_fill, sizeof(Message));

// Sort msg_data
                qsort(msg_data, (size_t)msg_index_end, sizeof(Message), msg_comp_active);

                for (i = 0; i < msg_index_end; i++) {
                    msg_index[i] = i;
                    if (msg_data[i].active == RECORD_NOTACTIVE) {
                        msg_index_end = i;
                        break;
                    }
                }

// Sort msg_index
                qsort(msg_index, (size_t)msg_index_end, sizeof(long *), msg_comp_data);

                // All done with this message.
                return;
            }
        }

        // Didn't find free message record.  Fetch some more space.
        // Get more msg_data space.
        m_ptr = realloc(msg_data, (msg_index_max+MSG_INCREMENT)*sizeof(Message));
        if (m_ptr) {
            msg_data = m_ptr;

            // Get more msg_index space
            m_ptr = realloc(msg_index, (msg_index_max+MSG_INCREMENT)*sizeof(Message *));
            if (m_ptr) {
                msg_index = m_ptr;
                msg_index_max += MSG_INCREMENT;

//fprintf(stderr, "Max Message Array: %ld\n", msg_index_max);

            }
            else {
            	// TODO
                //XtWarning("Unable to allocate more space for message index.\n");
            }
        }
        else {
        	// TODO
            //XtWarning("Unable to allocate more space for message database.\n");
        }
    }
    if (msg_index_end < msg_index_max) {
        msg_index[msg_index_end] = msg_index_end;

        // Copy message data into new message record.
        memcpy(&msg_data[msg_index_end++], m_fill, sizeof(Message));

// Sort msg_index
        qsort(msg_index, (size_t)msg_index_end, sizeof(long *), msg_comp_data);
    }
}





// Does a binary search through a sorted message database looking
// for a string match.
//
// If two or more messages match, this routine _should_ return the
// message with the latest timestamp.  This will ensure that earlier
// messages don't get mistaken for current messages, for the case
// where the remote station did a restart and is using the same
// sequence numbers over again.
//
long msg_find_data(Message *m_fill) {
    long record_start, record_mid, record_end, return_record, done;
    char tempfile[MAX_CALLSIGN+MAX_CALLSIGN+MAX_MESSAGE_ORDER+1];
    char tempfill[MAX_CALLSIGN+MAX_CALLSIGN+MAX_MESSAGE_ORDER+1];


    snprintf(tempfill, sizeof(tempfill), "%s%s%s",
            m_fill->call_sign,
            m_fill->from_call_sign,
            m_fill->seq);

    return_record = -1L;
    if (msg_index && msg_index_end >= 1) {
        /* more than one record */
         record_start=0L;
         record_end = (msg_index_end - 1);
         record_mid=(record_end-record_start)/2;

         done=0;
         while (!done) {

            /* get data for record start */
            snprintf(tempfile, sizeof(tempfile), "%s%s%s",
                    msg_data[msg_index[record_start]].call_sign,
                    msg_data[msg_index[record_start]].from_call_sign,
                    msg_data[msg_index[record_start]].seq);

            if (strcmp(tempfill, tempfile) < 0) {
                /* filename comes before */
                /*fprintf(stderr,"Before No data found!!\n");*/
                done=1;
                break;
            }
            else { /* get data for record end */

                snprintf(tempfile, sizeof(tempfile), "%s%s%s",
                        msg_data[msg_index[record_end]].call_sign,
                        msg_data[msg_index[record_end]].from_call_sign,
                        msg_data[msg_index[record_end]].seq);

                if (strcmp(tempfill,tempfile)>=0) { /* at end or beyond */
                    if (strcmp(tempfill, tempfile) == 0) {
                        return_record = record_end;
//fprintf(stderr,"record %ld",return_record);
                    }

                    done=1;
                    break;
                }
                else if ((record_mid == record_start) || (record_mid == record_end)) {
                    /* no mid for compare check to see if in the middle */
                    done=1;
                    snprintf(tempfile, sizeof(tempfile), "%s%s%s",
                            msg_data[msg_index[record_mid]].call_sign,
                            msg_data[msg_index[record_mid]].from_call_sign,
                            msg_data[msg_index[record_mid]].seq);
                    if (strcmp(tempfill,tempfile)==0) {
                        return_record = record_mid;
//fprintf(stderr,"record: %ld",return_record);
                    }
                }
            }
            if (!done) { /* get data for record mid */
                snprintf(tempfile, sizeof(tempfile), "%s%s%s",
                        msg_data[msg_index[record_mid]].call_sign,
                        msg_data[msg_index[record_mid]].from_call_sign,
                        msg_data[msg_index[record_mid]].seq);

                if (strcmp(tempfill, tempfile) == 0) {
                    return_record = record_mid;
//fprintf(stderr,"record %ld",return_record);
                    done = 1;
                    break;
                }

                if(strcmp(tempfill, tempfile)<0)
                    record_end = record_mid;
                else
                    record_start = record_mid;

                record_mid = record_start+(record_end-record_start)/2;
            }
        }
    }
    return(return_record);
}





void msg_replace_data(Message *m_fill, long record_num) {
    memcpy(&msg_data[msg_index[record_num]], m_fill, sizeof(Message));
}





void msg_get_data(Message *m_fill, long record_num) {
    memcpy(m_fill, &msg_data[msg_index[record_num]], sizeof(Message));
}





void msg_update_ack_stamp(long record_num) {

    //fprintf(stderr,"Attempting to update ack stamp: %ld\n",record_num);
    if ( (record_num >= 0) && (record_num < msg_index_end) ) {
        msg_data[msg_index[record_num]].last_ack_sent = sec_now();
        //fprintf(stderr,"Ack stamp: %ld\n",msg_data[msg_index[record_num]].last_ack_sent);
    }
    //fprintf(stderr,"\n\n\n*** Record: %ld ***\n\n\n",record_num);
}





// Called when we receive an ACK.  Sets the "acked" field in a
// Message which gets rid of the highlighting in the Send Message
// dialog for that message line.  This lets us know which messages
// have been acked and which have not.  If timeout is non-zero, then
// set acked to 2:  We use this in update_messages() to flag that
// "*TIMEOUT*" should prefix the string.  If cancelled is non-zero,
// set acked to 3:  We use this in update_messages() to flag that
// "*CANCELLED*" should prefix the string.
//
void msg_record_ack(char *to_call_sign,
                    char *my_call,
                    char *seq,
                    int timeout,
                    int cancel) {
    Message m_fill;
    long record;
    int do_update = 0;


    // Find the corresponding message in msg_data[i], set the
    // "acked" field to one.

    substr(m_fill.call_sign, to_call_sign, MAX_CALLSIGN);
    (void)remove_trailing_asterisk(m_fill.call_sign);

    substr(m_fill.from_call_sign, my_call, MAX_CALLSIGN);
    (void)remove_trailing_asterisk(m_fill.from_call_sign);

    substr(m_fill.seq, seq, MAX_MESSAGE_ORDER);
    (void)remove_trailing_spaces(m_fill.seq);
    (void)remove_leading_spaces(m_fill.seq);

    // Look for a message with the same to_call_sign, my_call,
    // and seq number
    record = msg_find_data(&m_fill);

    if (record == -1L) { // No match yet, try another tactic.
        if (seq[2] == '}' && strlen(seq) == 3) {

            // Try it again without the trailing '}' character
            m_fill.from_call_sign[2] = '\0';

            // Look for a message with the same to_call_sign,
            // my_call, and seq number (minus the trailing '}')
            record = msg_find_data(&m_fill);
        }
    }

    if(record != -1L) {     // Found a match!
        // Only cause an update if this is the first ack.  This
        // reduces dialog "flashing" a great deal
        if ( msg_data[msg_index[record]].acked == 0 ) {

            // Check for my callsign (including SSID).  If found,
            // update any open message dialogs
            if (is_my_call(msg_data[msg_index[record]].from_call_sign, 1) ) {

                //fprintf(stderr,"From: %s\tTo: %s\n",
                //    msg_data[msg_index[record]].from_call_sign,
                //    msg_data[msg_index[record]].call_sign);

                do_update++;
            }
        }
        else {  // This message has already been acked.
        }

        if (cancel)
            msg_data[msg_index[record]].acked = (char)3;
        else if (timeout)
            msg_data[msg_index[record]].acked = (char)2;
        else
            msg_data[msg_index[record]].acked = (char)1;

        // Set the interval to zero so that we don't display it
        // anymore in the dialog.  Same for tries.
        msg_data[msg_index[record]].interval = 0;
        msg_data[msg_index[record]].tries = 0;


    }


    if (do_update) {

        update_messages(1); // Force an update

        // Call check_popup_messages() here in order to pop up any
        // closed Send Message dialogs.  For first ack's or
        // CANCELLED messages it is less important, but for TIMEOUT
        // messages it is very important.
        //
// TODO
//        (void)check_popup_window(m_fill.call_sign, 2);  // Calls update_messages()
    }
}





// Called when we receive a REJ packet (reject).  Sets the "acked"
// field in a Message to 4 to indicate that the message has been
// rejected by the remote station.  This gets rid of the
// highlighting in the Send Message dialog for that message line.
// This lets us know which messages have been rejected and which
// have not.  We use this in update_messages() to flag that
// "*REJECTED*" should prefix the string.
//
// The most common source of REJ packets would be from sending to a
// D700A who's buffers are full, so that it can't take another
// message.
//
void msg_record_rej(char *to_call_sign,
                    char *my_call,
                    char *seq) {
    Message m_fill;
    long record;
    int do_update = 0;

    // Find the corresponding message in msg_data[i], set the
    // "acked" field to four.

    substr(m_fill.call_sign, to_call_sign, MAX_CALLSIGN);
    (void)remove_trailing_asterisk(m_fill.call_sign);

    substr(m_fill.from_call_sign, my_call, MAX_CALLSIGN);
    (void)remove_trailing_asterisk(m_fill.from_call_sign);

    substr(m_fill.seq, seq, MAX_MESSAGE_ORDER);
    (void)remove_trailing_spaces(m_fill.seq);
    (void)remove_leading_spaces(m_fill.seq);

    // Look for a message with the same to_call_sign, my_call,
    // and seq number
    record = msg_find_data(&m_fill);

    if (record == -1L) { // No match yet, try another tactic.
        if (seq[2] == '}' && strlen(seq) == 3) {

            // Try it again without the trailing '}' character
            m_fill.from_call_sign[2] = '\0';

            // Look for a message with the same to_call_sign,
            // my_call, and seq number (minus the trailing '}')
            record = msg_find_data(&m_fill);
        }
    }

    if(record != -1L) {     // Found a match!
        // Only cause an update if this is the first rej.  This
        // reduces dialog "flashing" a great deal
        if ( msg_data[msg_index[record]].acked == 0 ) {

            // Check for my callsign (including SSID).  If found,
            // update any open message dialogs
            if (is_my_call(msg_data[msg_index[record]].from_call_sign, 1) ) {

                //fprintf(stderr,"From: %s\tTo: %s\n",
                //    msg_data[msg_index[record]].from_call_sign,
                //    msg_data[msg_index[record]].call_sign);

                do_update++;
            }
        }
        else {  // This message has already been acked.
        }

        // Actually record the REJ here
        msg_data[msg_index[record]].acked = (char)4;

        // Set the interval to zero so that we don't display it
        // anymore in the dialog.  Same for tries.
        msg_data[msg_index[record]].interval = 0;
        msg_data[msg_index[record]].tries = 0;

    }


    if (do_update) {

        update_messages(1); // Force an update

        // Call check_popup_messages() here in order to pop up any
        // closed Send Message dialogs.  For first ack's or
        // CANCELLED messages it is less important, but for TIMEOUT
        // messages it is very important.
        //
// TODO
//        (void)check_popup_window(m_fill.call_sign, 2);  // Calls update_messages()
    }
}





// Called from check_and_transmit_messages().  Updates the interval
// field in our message record for the message currently being
// transmitted.  We'll use this in the Send Message dialog to
// display the current message interval.
//
void msg_record_interval_tries(char *to_call_sign,
                    char *my_call,
                    char *seq,
                    time_t interval,
                    int tries) {
    Message m_fill;
    long record;


    // Find the corresponding message in msg_data[i]

    substr(m_fill.call_sign, to_call_sign, MAX_CALLSIGN);
    (void)remove_trailing_asterisk(m_fill.call_sign);

    substr(m_fill.from_call_sign, my_call, MAX_CALLSIGN);
    (void)remove_trailing_asterisk(m_fill.from_call_sign);

    substr(m_fill.seq, seq, MAX_MESSAGE_ORDER);
    (void)remove_trailing_spaces(m_fill.seq);
    (void)remove_leading_spaces(m_fill.seq);

    // Look for a message with the same to_call_sign, my_call,
    // and seq number
    record = msg_find_data(&m_fill);
    if(record != -1L) {     // Found a match!

        msg_data[msg_index[record]].interval = interval;
        msg_data[msg_index[record]].tries = tries;
    }

    update_messages(1); // Force an update
}





// Returns: time_t for last_ack_sent
//          -1 if the message doesn't pass our tests
//           0 if it is a new message.
//
// Also returns the record number found if not passed a NULL pointer
// in record_out or -1L if it's a new record.
//
time_t msg_data_add(char *call_sign, char *from_call, char *data,
        char *seq, char type, TAprsPort port, long *record_out) {
    Message m_fill;
    long record;
    char time_data[MAX_TIME];
    int do_msg_update = 0;
    time_t last_ack_sent;
    int distance = -1;
    char temp[10];
    int group_message = 0;


//fprintf(stderr,"from:%s, to:%s, seq:%s\n", from_call, call_sign, seq);

    // Set the default output condition.  We'll change this later if
    // we need to.
    if (record_out != NULL)
        *record_out = -1l;

    // Check for some reasonable string in call_sign parameter
    if (call_sign == NULL || strlen(call_sign) == 0) {

        return((time_t)-1l);
    }
//else
//fprintf(stderr,"msg_data_add():call_sign: %s\n", call_sign);

    if ( (data != NULL) && (strlen(data) > MAX_MESSAGE_LENGTH) ) {

        return((time_t)-1l);
    }

    substr(m_fill.call_sign, call_sign, MAX_CALLSIGN);
    (void)remove_trailing_asterisk(m_fill.call_sign);

    substr(m_fill.from_call_sign, from_call, MAX_CALLSIGN);
    (void)remove_trailing_asterisk(m_fill.call_sign);

    substr(m_fill.seq, seq, MAX_MESSAGE_ORDER);
    (void)remove_trailing_spaces(m_fill.seq);
    (void)remove_leading_spaces(m_fill.seq);

// If the sequence number is blank, then it may have been a query,
// directed query, or group message.  Assume it is a new message in
// each case and add it.

    if (seq[0] != '\0') {   // Normal station->station messaging or
                            // bulletins
        // Look for a message with the same call_sign,
        // from_call_sign, and seq number
        record = msg_find_data(&m_fill);
//fprintf(stderr,"RECORD %ld  \n",record);
//fprintf(stderr,"Normal station->station message\n");
    }
    else {  // Group message/query/etc.
        record = -1L;
        group_message++;    // Flag it as a group message
//fprintf(stderr,"Group message/query/etc\n");
    }
    msg_clear_data(&m_fill);
    if(record != -1L) { /* fill old data */
        msg_get_data(&m_fill, record);
        last_ack_sent = m_fill.last_ack_sent;
        //fprintf(stderr,"Found: last_ack_sent: %ld\n",m_fill.last_ack_sent);

        //fprintf(stderr,"Found a duplicate message.  Updating fields, seq %s\n",seq);

        // If message is different this time, do an update to the
        // send message window and update the sec_heard field.  The
        // remote station must have restarted and is re-using the
        // sequence numbers.  What a pain!
        if (strcmp(m_fill.message_line,data) != 0) {
            m_fill.sec_heard = sec_now();
            last_ack_sent = (time_t)0;
//fprintf(stderr,"Message is different this time: Setting last_ack_sent to 0\n");

            if (type != MESSAGE_BULLETIN) { // Not a bulletin
                do_msg_update++;
            }
        }

        // If message is the same, but the sec_heard field is quite
        // old (more than 8 hours), the remote station must have
        // restarted, is re-using the sequence numbers, and just
        // happened to send the same message with the same sequence
        // number.  Again, what a pain!  Either that, or we
        // connected to a spigot with a _really_ long queue!
        if (m_fill.sec_heard < (sec_now() - (8 * 60 * 60) )) {
            m_fill.sec_heard = sec_now();
            last_ack_sent = (time_t)0;
//fprintf(stderr,"Found >8hrs old: Setting last_ack_sent to 0\n");

            if (type != MESSAGE_BULLETIN) { // Not a bulletin
                do_msg_update++;
            }
        }

        // Check for zero time
        if (m_fill.sec_heard == (time_t)0) {
            m_fill.sec_heard = sec_now();
            fprintf(stderr,"Zero time on a previous message.\n");
        }
    }
    else {
        // Only do this if it's a new message.  This keeps things
        // more in sequence by not updating the time stamps
        // constantly on old messages that don't get ack'ed.
        m_fill.sec_heard = sec_now();
        last_ack_sent = (time_t)0;
        //fprintf(stderr,"New msg: Setting last_ack_sent to 0\n");

        if (type != MESSAGE_BULLETIN) { // Not a bulletin
//fprintf(stderr,"Found new message\n");
            do_msg_update++;    // Always do an update to the
                                // message window for new messages
        }
    }

    /* FROM */
    m_fill.port =port;
    m_fill.active=RECORD_ACTIVE;
    m_fill.type=type;
    if (m_fill.heard_via_tnc != VIA_TNC)
        m_fill.heard_via_tnc = (port == APRS_PORT_TTY) ? VIA_TNC : NOT_VIA_TNC;

    distance = (int)(distance_from_my_station(from_call,temp, sizeof(temp)) + 0.9999);

    if (distance != 0) {    // Have a posit from the sending station
        m_fill.position_known = 1;
        //fprintf(stderr,"Position known: %s\n",from_call);
    }
    else {
        //fprintf(stderr,"Position not known: %s\n",from_call);
    }

    substr(m_fill.call_sign,call_sign,MAX_CALLSIGN);
    (void)remove_trailing_asterisk(m_fill.call_sign);

    substr(m_fill.from_call_sign,from_call,MAX_CALLSIGN);
    (void)remove_trailing_asterisk(m_fill.from_call_sign);

    // Update the message field
    substr(m_fill.message_line,data,MAX_MESSAGE_LENGTH);

    substr(m_fill.seq,seq,MAX_MESSAGE_ORDER);
    (void)remove_trailing_spaces(m_fill.seq);
    (void)remove_leading_spaces(m_fill.seq);

    // Create a timestamp from the current time
    snprintf(m_fill.packet_time,
        sizeof(m_fill.packet_time),
        "%s",
        get_time(time_data));

    if(record == -1L) {     // No old record found
        if (group_message)
            m_fill.acked = 1;   // Group msgs/queries need no ack
        else
            m_fill.acked = 0;   // We can't have been acked yet

        m_fill.interval = 0;
        m_fill.tries = 0;

        // We'll be sending an ack right away if this is a new
        // message, so might as well set the time now so that we
        // don't care about failing to set it in
        // msg_update_ack_stamp due to the record number being -1.
        m_fill.last_ack_sent = sec_now();

        msg_input_database(&m_fill);    // Create a new entry
        //fprintf(stderr,"No record found: Setting last_ack_sent to sec_now()00\n");
    }
    else {  // Old record found
        //fprintf(stderr,"Replacing the message in the database, seq %s\n",seq);
        msg_replace_data(&m_fill, record);  // Copy fields from m_fill to record
    }

    /* display messages */
// TODO
 //   if (type == MESSAGE_MESSAGE)
//        all_messages(from,call_sign,from_call,data);

    // Check for my callsign (including SSID).  If found, update any
    // open message dialogs
    if (       is_my_call(m_fill.from_call_sign, 1)
            || is_my_call(m_fill.call_sign, 1) ) {

        if (do_msg_update) {
            update_messages(1); // Force an update
        }
    }


    // Return the important variables we'll need
    if (record_out != NULL)
        *record_out = record;

//fprintf(stderr,"\nrecord_out:%ld record %ld\n",*record_out,record);
    return(last_ack_sent);

}   // End of msg_data_add()





// alert_data_add:  Function which adds NWS weather alerts to the
// alert hash.
//
// This function adds alerts directly to the alert hash, bypassing
// the message list and associated message-scan functions.
//
void alert_data_add(char *call_sign, char *from_call, char *data,
        char *seq, char type, TAprsPort port) {
    Message m_fill;
    char time_data[MAX_TIME];



/*
    if (log_wx_alert_data && from != DATA_VIA_FILE) {
        char temp_msg[MAX_MESSAGE_LENGTH+1];

        // Attempt to reconstruct the original weather alert packet
        // here, minus the path.
        snprintf(temp_msg,
            sizeof(temp_msg),
            "%s>APRS::%-9s:%s{%s",
            from_call,
            call_sign,
            data,
            seq);
        log_data( get_user_base_dir(LOGFILE_WX_ALERT), temp_msg);
//        fprintf(stderr, "%s\n", temp_msg);
    }
*/

    if ( (data != NULL) && (strlen(data) > MAX_MESSAGE_LENGTH) ) {
        return;
    }

    substr(m_fill.call_sign, call_sign, MAX_CALLSIGN);
    (void)remove_trailing_asterisk(m_fill.call_sign);

    substr(m_fill.from_call_sign, from_call, MAX_CALLSIGN);
    (void)remove_trailing_asterisk(m_fill.call_sign);

    substr(m_fill.seq, seq, MAX_MESSAGE_ORDER);
    (void)remove_trailing_spaces(m_fill.seq);
    (void)remove_leading_spaces(m_fill.seq);

    m_fill.sec_heard = sec_now();

    /* FROM */
    m_fill.port=port;
    m_fill.active=RECORD_ACTIVE;
    m_fill.type=type;

    // We don't have a value filled in yet here!
    //if (m_fill.heard_via_tnc != VIA_TNC)
    m_fill.heard_via_tnc = (port == APRS_PORT_TTY) ? VIA_TNC : NOT_VIA_TNC;

    substr(m_fill.call_sign,call_sign,MAX_CALLSIGN);
    (void)remove_trailing_asterisk(m_fill.call_sign);

    substr(m_fill.from_call_sign,from_call,MAX_CALLSIGN);
    (void)remove_trailing_asterisk(m_fill.from_call_sign);

    // Update the message field
    substr(m_fill.message_line,data,MAX_MESSAGE_LENGTH);

    substr(m_fill.seq,seq,MAX_MESSAGE_ORDER);
    (void)remove_trailing_spaces(m_fill.seq);
    (void)remove_leading_spaces(m_fill.seq);

    // Create a timestamp from the current time
    snprintf(m_fill.packet_time,
        sizeof(m_fill.packet_time),
        "%s",
        get_time(time_data));

    // Go try to add it to our alert hash.  alert_build_list() will
    // check for duplicates before adding it.

// TODO
//    alert_build_list(&m_fill);

    // This function fills in the Shapefile filename and index
    // so that we can later draw it.
// TODO
//    fill_in_new_alert_entries();


}   // End of alert_data_add()





// What I'd like to do for the following routine:  Use
// XmTextGetInsertionPosition() or XmTextGetCursorPosition() to
// find the last of the text.  Could also save the position for
// each SendMessage window.  Compare the timestamps of messages
// found with the last update time.  If newer, then add them to
// the end.  This should stop the incessant scrolling.

// Another idea, easier method:  Create a buffer.  Snag out the
// messages from the array and sort by time.  Put them into a
// buffer.  Figure out the length of the text widget, and append
// the extra length of the buffer onto the end of the text widget.
// Once the message data is turned into a linked list, it might
// be sorted already by time, so this window will look better
// anyway.

// Calling update_messages with force == 1 will cause an update
// no matter what message_update_time() says.
void update_messages(int force) {
    // TODO
}
/*
void update_messages(int force) {
    static XmTextPosition pos;
    char temp1[MAX_CALLSIGN+1];
    char temp2[500];
    char stemp[20];
    long i;
    int mw_p;
    char *temp_ptr;


    if ( message_update_time() || force) {

//fprintf(stderr,"update_messages()\n");

        //fprintf(stderr,"Um %d\n",(int)sec_now() );

        // go through all mw_p's!

        // Perform this for each message window
        for (mw_p=0; msg_index && mw_p < MAX_MESSAGE_WINDOWS; mw_p++) {
            //pos=0;

begin_critical_section(&send_message_dialog_lock, "db.c:update_messages" );

            if (mw[mw_p].send_message_dialog!=NULL) {

//fprintf(stderr,"\n");

//fprintf(stderr,"found send_message_dialog\n");

                // Clear the text from message window
                XmTextReplace(mw[mw_p].send_message_text,
                    (XmTextPosition) 0,
                    XmTextGetLastPosition(mw[mw_p].send_message_text),
                    "");

                // Snag the callsign you're dealing with from the message dialogue
                if (mw[mw_p].send_message_call_data != NULL) {
                    temp_ptr = XmTextFieldGetString(mw[mw_p].send_message_call_data);
                    snprintf(temp1,
                        sizeof(temp1),
                        "%s",
                        temp_ptr);
                    XtFree(temp_ptr);

                    new_message_data--;
                    if (new_message_data<0)
                        new_message_data=0;

                    if(strlen(temp1)>0) {   // We got a callsign from the dialog so
                        // create a linked list of the message indexes in time-sorted order

                        typedef struct _index_record {
                            int index;
                            time_t sec_heard;
                            struct _index_record *next;
                        } index_record;
                        index_record *head = NULL;
                        index_record *p_prev = NULL;
                        index_record *p_next = NULL;

                        // Allocate the first record (a dummy record)
                        head = (index_record *)malloc(sizeof(index_record));
                        CHECKMALLOC(head);

                        head->index = -1;
                        head->sec_heard = (time_t)0;
                        head->next = NULL;

                        (void)remove_trailing_spaces(temp1);
                        (void)to_upper(temp1);

                        pos = 0;
                        // Loop through looking for messages to/from
                        // that callsign (including SSID)
                        for (i = 0; i < msg_index_end; i++) {
                            if (msg_data[msg_index[i]].active == RECORD_ACTIVE
                                    && (strcmp(temp1, msg_data[msg_index[i]].from_call_sign) == 0
                                        || strcmp(temp1,msg_data[msg_index[i]].call_sign) == 0)
                                    && (is_my_call(msg_data[msg_index[i]].from_call_sign, 1)
                                        || is_my_call(msg_data[msg_index[i]].call_sign, 1)
                                        || mw[mw_p].message_group ) ) {
                                int done = 0;

                                // Message matches our parameters so
                                // save the relevant data about the
                                // message in our linked list.  Compare
                                // the sec_heard field to see whether
                                // we're higher or lower, and insert the
                                // record at the correct spot in the
                                // list.  We end up with a time-sorted
                                // list.
                                p_prev  = head;
                                p_next = p_prev->next;
                                while (!done && (p_next != NULL)) {  // Loop until end of list or record inserted

                                    //fprintf(stderr,"Looping, looking for insertion spot\n");

                                    if (p_next->sec_heard <= msg_data[msg_index[i]].sec_heard) {
                                        // Advance one record
                                        p_prev = p_next;
                                        p_next = p_prev->next;
                                    }
                                    else {  // We found the correct insertion spot
                                        done++;
                                    }
                                }

                                //fprintf(stderr,"Inserting\n");

                                // Add the record in between p_prev and
                                // p_next, even if we're at the end of
                                // the list (in that case p_next will be
                                // NULL.
                                p_prev->next = (index_record *)malloc(sizeof(index_record));
                                CHECKMALLOC(p_prev->next);

                                p_prev->next->next = p_next; // Link to rest of records or NULL
                                p_prev->next->index = i;
                                p_prev->next->sec_heard = msg_data[msg_index[i]].sec_heard;
// Remember to free this entire linked list before exiting the loop for
// this message window!
                            }
                        }
                        // Done processing the entire list for this
                        // message window.

                        //fprintf(stderr,"Done inserting/looping\n");

                        if (head->next != NULL) {   // We have messages to display
                            int done = 0;

                            //fprintf(stderr,"We have messages to display\n");

                            // Run through the linked list and dump the
                            // info out.  It's now in time-sorted order.

// Another optimization would be to keep a count of records added, then
// later when we were dumping it out to the window, only dump the last
// XX records out.

                            p_prev = head->next;    // Skip the first dummy record
                            p_next = p_prev->next;
                            while (!done && (p_prev != NULL)) {  // Loop until end of list
                                int j = p_prev->index;  // Snag the index out of the record
                                char prefix[50];
                                char interval_str[50];
                                int offset = 22;    // Offset for highlighting


                                //fprintf(stderr,"\nLooping through, reading messages\n");

//fprintf(stderr,"acked: %d\n",msg_data[msg_index[j]].acked);

                                // Message matches so snag the important pieces into a string
                                snprintf(stemp, sizeof(stemp),
                                    "%c%c/%c%c %c%c:%c%c",
                                    msg_data[msg_index[j]].packet_time[0],
                                    msg_data[msg_index[j]].packet_time[1],
                                    msg_data[msg_index[j]].packet_time[2],
                                    msg_data[msg_index[j]].packet_time[3],
                                    msg_data[msg_index[j]].packet_time[8],
                                    msg_data[msg_index[j]].packet_time[9],
                                    msg_data[msg_index[j]].packet_time[10],
                                    msg_data[msg_index[j]].packet_time[11]
                                );

// Somewhere in here we appear to be losing the first message.  It
// doesn't get written to the window later in the QSO.  Same for
// closing the window and re-opening it, putting the same callsign
// in and pressing "New Call" button.  First message is missing.

                                // Label the message line with who sent it.
                                // If acked = 2 a timeout has occurred
                                // If acked = 3 a cancel has occurred
                                if (msg_data[msg_index[j]].acked == 2) {
                                    snprintf(prefix,
                                        sizeof(prefix),
                                        "%s ",
                                        langcode("WPUPMSB016") ); // "*TIMEOUT*"
                                }
                                else if (msg_data[msg_index[j]].acked == 3) {
                                    snprintf(prefix,
                                        sizeof(prefix),
                                        "%s ",
                                        langcode("WPUPMSB017") ); // "*CANCELLED*"
                                }
                                else if (msg_data[msg_index[j]].acked == 4) {
                                    snprintf(prefix,
                                        sizeof(prefix),
                                        "%s ",
                                        langcode("WPUPMSB018") ); // "*REJECTED*"
                                }
                                else prefix[0] = '\0';

                                if (msg_data[msg_index[j]].interval) {
                                    snprintf(interval_str,
                                        sizeof(interval_str),
                                        ">%d/%lds",
                                        msg_data[msg_index[j]].tries + 1,
                                        (long)msg_data[msg_index[j]].interval);

                                    // Don't highlight the interval
                                    // value
                                    offset = offset + strlen(interval_str);
                                }
                                else {
                                    interval_str[0] = '\0';
                                }

                                snprintf(temp2, sizeof(temp2),
                                    "%s %-9s%s>%s%s\n",
                                    // Debug code.  Trying to find sorting error
                                    //"%ld  %s  %-9s>%s\n",
                                    //msg_data[msg_index[j]].sec_heard,
                                    stemp,
                                    msg_data[msg_index[j]].from_call_sign,
                                    interval_str,
                                    prefix,
                                    msg_data[msg_index[j]].message_line);

//fprintf(stderr,"message: %s\n", msg_data[msg_index[j]].message_line);
//fprintf(stderr,"update_messages: %s|%s", temp1, temp2);

                                // Replace the text from pos to pos+strlen(temp2) by the string "temp2"
                                if (mw[mw_p].send_message_text != NULL) {

                                    // Insert the text at the end
//                                    XmTextReplace(mw[mw_p].send_message_text,
//                                            pos,
//                                            pos+strlen(temp2),
//                                            temp2);

                                    XmTextInsert(mw[mw_p].send_message_text,
                                            pos,
                                            temp2);

                                    // Set highlighting based on the
                                    // "acked" field.  Callsign
                                    // match here includes SSID.
//fprintf(stderr,"acked: %d\t",msg_data[msg_index[j]].acked);
                                    if ( (msg_data[msg_index[j]].acked == 0)    // Not acked yet
                                            && ( is_my_call(msg_data[msg_index[j]].from_call_sign, 1)) ) {
//fprintf(stderr,"Setting underline\t");
                                        XmTextSetHighlight(mw[mw_p].send_message_text,
                                            pos+offset,
                                            pos+strlen(temp2),
                                            //XmHIGHLIGHT_SECONDARY_SELECTED); // Underlining
                                            XmHIGHLIGHT_SELECTED);         // Reverse Video
                                    }
                                    else {  // Message was acked, get rid of highlighting
//fprintf(stderr,"Setting normal\t");
                                        XmTextSetHighlight(mw[mw_p].send_message_text,
                                            pos+offset,
                                            pos+strlen(temp2),
                                            XmHIGHLIGHT_NORMAL);
                                    }

//fprintf(stderr,"Text: %s\n",temp2);

                                    pos += strlen(temp2);

                                }

                                // Advance to the next record in the list
                                p_prev = p_next;
                                if (p_next != NULL)
                                    p_next = p_prev->next;

                            }   // End of while
                        }   // End of if
                        else {  // No messages matched, list is empty
                        }

// What does this do?  Move all of the text?
//                        if (pos > 0) {
//                            if (mw[mw_p].send_message_text != NULL) {
//                                XmTextReplace(mw[mw_p].send_message_text,
//                                        --pos,
//                                        XmTextGetLastPosition(mw[mw_p].send_message_text),
//                                        "");
//                            }
//                        }

                        //fprintf(stderr,"Free'ing list\n");

                        // De-allocate the linked list
                        p_prev = head;
                        while (p_prev != NULL) {

                            //fprintf(stderr,"You're free!\n");

                            p_next = p_prev->next;
                            free(p_prev);
                            p_prev = p_next;
                        }

                        // Show the last added message in the window
                        XmTextShowPosition(mw[mw_p].send_message_text,
                            pos);
                    }
                }
            }

end_critical_section(&send_message_dialog_lock, "db.c:update_messages" );

        }
        last_message_update = sec_now();

//fprintf(stderr,"Message index end: %ld\n",msg_index_end);

    }
}
*/




void mdelete_messages_from(char *from) {
    long i;

    // Mark message records with RECORD_NOTACTIVE.  This will mark
    // them for re-use.
    for (i = 0; msg_index && i < msg_index_end; i++)
        if (strcmp(msg_data[i].call_sign, _aprs_mycall) == 0
        		&& strcmp(msg_data[i].from_call_sign, from) == 0)
            msg_data[i].active = RECORD_NOTACTIVE;
}





void mdelete_messages_to(char *to) {
    long i;

    // Mark message records with RECORD_NOTACTIVE.  This will mark
    // them for re-use.
    for (i = 0; msg_index && i < msg_index_end; i++)
        if (strcmp(msg_data[i].call_sign, to) == 0)
            msg_data[i].active = RECORD_NOTACTIVE;
}





void mdelete_messages(char *to_from) {
    long i;

    // Mark message records with RECORD_NOTACTIVE.  This will mark
    // them for re-use.
    for (i = 0; msg_index && i < msg_index_end; i++)
        if (strcmp(msg_data[i].call_sign, to_from) == 0 || strcmp(msg_data[i].from_call_sign, to_from) == 0)
            msg_data[i].active = RECORD_NOTACTIVE;
}





void mdata_delete_type(const char msg_type, const time_t reference_time) {
    long i;

    // Mark message records with RECORD_NOTACTIVE.  This will mark
    // them for re-use.
    for (i = 0; msg_index && i < msg_index_end; i++)

        if ((msg_type == '\0' || msg_type == msg_data[i].type)
                && msg_data[i].active == RECORD_ACTIVE
                && msg_data[i].sec_heard < reference_time)

            msg_data[i].active = RECORD_NOTACTIVE;
}





void check_message_remove(time_t curr_sec) {       // called in timing loop

    // Time to check for old messages again?  (Currently every ten
    // minutes)
#ifdef EXPIRE_DEBUG
    if ( last_message_remove < (curr_sec - DEBUG_MESSAGE_REMOVE_CYCLE) ) {
#else // EXPIRE_DEBUG
    if ( last_message_remove < (curr_sec - MESSAGE_REMOVE_CYCLE) ) {
#endif

        // Yes it is.  Mark all messages that are older than
        // sec_remove with the RECORD_NOTACTIVE flag.  This will
        // mark them for re-use.
#ifdef EXPIRE_DEBUG
        mdata_delete_type('\0', curr_sec-DEBUG_MESSAGE_REMOVE);
#else   // EXPIRE_DEBUG
        mdata_delete_type('\0', curr_sec-_aprs_sec_remove);
#endif

        last_message_remove = curr_sec;
    }

    // Should we sort them at this point so that the unused ones are
    // near the end?  It looks like the message input functions do
    // this, so I guess we don't need to do it here.
}





void mscan_file(char msg_type, void (*function)(Message *)) {
    long i;

    for (i = 0; msg_index && i < msg_index_end; i++)
        if ((msg_type == '\0' || msg_type == msg_data[msg_index[i]].type) &&
                msg_data[msg_index[i]].active == RECORD_ACTIVE)
            function(&msg_data[msg_index[i]]);
}





void mprint_record(Message *m_fill) {
/*
    fprintf(stderr,
        "%-9s>%-9s %s:%5s %s:%c :%s\n",
        m_fill->from_call_sign,
        m_fill->call_sign,
        langcode("WPUPMSB013"), // "seq"
        m_fill->seq,
        langcode("WPUPMSB014"), // "type"
        m_fill->type,
        m_fill->message_line);
*/
}





void mdisplay_file(char msg_type) {
    fprintf(stderr,"\n\n");
    mscan_file(msg_type, mprint_record);
    fprintf(stderr,"\tmsg_index_end %ld, msg_index_max %ld\n", msg_index_end, msg_index_max);
}

void pad_callsign(char *callsignout, char *callsignin) {
    int i,l;

    l=(int)strlen(callsignin);
    for(i=0; i<9;i++) {
        if(i<l) {
            if(isalnum((int)callsignin[i]) || callsignin[i]=='-') {
                callsignout[i]=callsignin[i];
            }
            else {
                callsignout[i] = ' ';
            }
        }
        else {
            callsignout[i] = ' ';
        }
    }
    callsignout[i] = '\0';
}



int decode_message(gchar *call,gchar *path,gchar *message,gint port,gint third_party)
{
    char *temp_ptr;
    //char ipacket_message[300];
    char message_plus_acks[MAX_MESSAGE_LENGTH + 10];
    char from_call[MAX_CALLSIGN+1];
    char ack[20];
    int ok, len;
    char addr[9+1];
    char addr9[9+1];
    char msg_id[5+1];
    char orig_msg_id[5+1];
    char ack_string[6];
    int done;
    int reply_ack = 0;
    int to_my_call = 0;
    int to_my_base_call = 0;
    int from_my_call = 0;


    // :xxxxxxxxx:____0-67____             message              printable, except '|', '~', '{'
    // :BLNn     :____0-67____             general bulletin     printable, except '|', '~'
    // :BLNnxxxxx:____0-67____           + Group Bulletin
    // :BLNX     :____0-67____             Announcement
    // :NWS-xxxxx:____0-67____             NWS Service Bulletin
    // :NWS_xxxxx:____0-67____             NWS Service Bulletin
    // :xxxxxxxxx:ackn1-5n               + ack
    // :xxxxxxxxx:rejn1-5n               + rej
    // :xxxxxxxxx:____0-67____{n1-5n     + message
    // :NTS....
    //  01234567890123456
    // 01234567890123456    old
    // we get message with already extracted data ID


    if (is_my_call(call, 1) ) { // Check SSID also
        from_my_call++;
    }

    ack_string[0] = '\0';   // Clear out the Reply/Ack result string

    len = (int)strlen(message);
    ok = (int)(len > 9 && message[9] == ':');

    if (ok) {

//fprintf(stderr,"DEBUG: decode_message: from %s: %s\n", call, message);
//return 0;

        substr(addr9,message,9); // extract addressee
        snprintf(addr,
            sizeof(addr),
            "%s",
            addr9);
        (void)remove_trailing_spaces(addr);

        if (is_my_call(addr,1)) { // Check includes SSID
            to_my_call++;
        }

        if (is_my_call(addr,0)) { // Check ignores SSID.  We use
                                  // this to catch messages to some
                                  // of our other SSID's
            to_my_base_call++;
        }

        message = message + 10; // pointer to message text

        // Save the message text and the acks/reply-acks before we
        // extract the acks below.
        snprintf(message_plus_acks,
            sizeof(message_plus_acks),
            "%s",
            message);

        temp_ptr = strrchr(message,'{'); // look for message ID after
                                         //*last* { in message.
        msg_id[0] = '\0';
        if (temp_ptr != NULL) {
            substr(msg_id,temp_ptr+1,5); // extract message ID, could be non-digit
            temp_ptr[0] = '\0';          // adjust message end (chops off message ID)
        }

        // Save the original msg_id away.
        snprintf(orig_msg_id,
            sizeof(orig_msg_id),
            "%s",
            msg_id);

        // Check for Reply/Ack protocol in msg_id, which looks like
        // this:  "{XX}BB", where XX is the sequence number for the
        // message, and BB is the ack for the previous message from
        // my station.  I've also seen this from APRS+: "{XX}B", so
        // perhaps this is also possible "{X}B" or "{X}BB}".  We can
        // also get auto-reply responses from APRS+ that just have
        // "}X" or "}XX" at the end.  We decode those as well.
        //

        temp_ptr = strstr(msg_id,"}"); // look for Reply Ack in msg_id

        if (temp_ptr != NULL) { // Found Reply/Ack protocol!

            reply_ack++;

// Put this code into the UI message area as well (if applicable).

            // Separate out the extra ack so that we can deal with
            // it properly.
            snprintf(ack_string,
                sizeof(ack_string),
                "%s",
                temp_ptr+1); // After the '}' character!

            // Terminate it here so that rest of decode works
            // properly.  We can get duplicate messages
            // otherwise.
            //
// Note that we modify msg_id here.  Use orig_msg_id if we need the
// unmodified version (full REPLY-ACK version) later.
            //
            temp_ptr[0] = '\0'; // adjust msg_id end

        }
        else {  // Look for Reply Ack in message without sequence
                // number
            temp_ptr = strstr(message,"}");

            if (temp_ptr != NULL) {
                int yy = 0;

                reply_ack++;

// Put this code into the UI message area as well (if applicable).
                snprintf(ack_string,
                    sizeof(ack_string),
                    "%s",
                    temp_ptr+1);    // After the '}' character!

                ack_string[yy] = '\0';  // Terminate the string

                // Terminate it here so that rest of decode works
                // properly.  We can get duplicate messages
                // otherwise.
                temp_ptr[0] = '\0'; // adjust message end
            }
        }

        done = 0;
    }
    else {
        done = 1;                               // fall through...
    }

    len = (int)strlen(message);
    //--------------------------------------------------------------------------
    if (!done && len > 3 && strncmp(message,"ack",3) == 0) {              // ACK

        // Received an ACK packet.  Note that these can carry the
        // REPLY-ACK protocol or a single ACK sequence number plus
        // perhaps an extra '}' on the end.  They should have one of
        // these formats:
        //      ack1        Normal ACK
        //      ackY        Normal ACK
        //      ack23       Normal ACK
        //      ackfH       Normal ACK
        //      ack23{      REPLY-ACK Protocol
        //      ack2Q}3d    REPLY-ACK Protocol

        substr(msg_id,message+3,5);
        // fprintf(stderr,"ACK: %s: |%s| |%s|\n",call,addr,msg_id);

        if (to_my_call) { // Check SSID also

            // Note:  This function handles REPLY-ACK protocol just
            // fine, stripping off the 2nd ack if present.  It uses
            // only the first sequence number.
            clear_acked_message(call,addr,msg_id);  // got an ACK for me

            // This one also handles REPLY-ACK protocol just fine.
            msg_record_ack(call,addr,msg_id,0,0);   // Record the ack for this message
        }
        else {  // ACK is for another station
            // Now if I have Igate on and I allow to retransmit station data
            // check if this message is to a person I have heard on my TNC within an X
            // time frame. If if is a station I heard and all the conditions are ok
            // spit the ACK out on the TNC -FG
/*
 * TODO - Add igate support
            if (operate_as_an_igate>1
                    && from==DATA_VIA_NET
                    && !from_my_call     // Check SSID also
                    && port != -1) {    // Not from a log file
                char short_path[100];

                shorten_path(path,short_path,sizeof(short_path));

                // Only send '}' and the ack_string if it's not
                // empty, else just end the packet with the message
                // string.  This keeps us from appending a '}' when
                // it's not called for.
                snprintf(ipacket_message,
                    sizeof(ipacket_message),
                    "}%s>%s,TCPIP,%s*::%s:%s",

                    call,
                    short_path,
                    my_callsign,
                    addr9,
                    message_plus_acks);


                output_igate_rf(call,
                    addr,
                    path,
                    ipacket_message,
                    port,
                    third_party,
                    NULL);

                igate_msgs_tx++;
            }
*/
        }
        done = 1;
    }
    //--------------------------------------------------------------------------
    if (!done && len > 3 && strncmp(message,"rej",3) == 0) {              // REJ

        substr(msg_id,message+3,5);

        if (to_my_call) {   // Check SSID also

            // Note:  This function handles REPLY-ACK protocol just
            // fine, stripping off the 2nd ack if present.  It uses
            // only the first sequence number.
            clear_acked_message(call,addr,msg_id);  // got an REJ for me

            // This one also handles REPLY-ACK protocol just fine.
            msg_record_rej(call,addr,msg_id);   // Record the REJ for this message
        }
        else {  // REJ is for another station
            /* Now if I have Igate on and I allow to retransmit station data           */
            /* check if this message is to a person I have heard on my TNC within an X */
            /* time frame. If if is a station I heard and all the conditions are ok    */
            /* spit the REJ out on the TNC                                             */

/*
 * TODO - Add igate support
            if (operate_as_an_igate>1
                    && from==DATA_VIA_NET
                    && !from_my_call    // Check SSID also
                    && port != -1) {    // Not from a log file
                char short_path[100];

                shorten_path(path,short_path,sizeof(short_path));

                // Only send '}' and the rej_string if it's not
                // empty, else just end the packet with the message
                // string.  This keeps us from appending a '}' when
                // it's not called for.
                snprintf(ipacket_message,
                    sizeof(ipacket_message),
                    "}%s>%s,TCPIP,%s*::%s:%s",

                    call,
                    short_path,
                    my_callsign,
                    addr9,
                    message_plus_acks);


                output_igate_rf(call,
                    addr,
                    path,
                    ipacket_message,
                    port,
                    third_party,
                    NULL);

                igate_msgs_tx++;
            }
*/
        }

        done = 1;
    }

    //--------------------------------------------------------------------------
    if (!done && strncmp(addr,"BLN",3) == 0) {                       // Bulletin
        // fprintf(stderr,"found BLN: |%s| |%s|\n",addr,message);
        bulletin_data_add(addr,call,message,"",MESSAGE_BULLETIN,port);
        done = 1;
    }

    //--------------------------------------------------------------------------
    if (!done && strlen(msg_id) > 0 && to_my_call) {         // Message for me (including SSID check)
                                                             // with msg_id (sequence number)
        time_t last_ack_sent;
        long record;


// Remember to put this code into the UI message area as well (if
// applicable).

        // Check for Reply/Ack
        if (reply_ack && strlen(ack_string) != 0) { // Have a free-ride ack to deal with

            clear_acked_message(call,addr,ack_string);  // got an ACK for me

            msg_record_ack(call,addr,ack_string,0,0);   // Record the ack for this message
        }

        // Save the ack 'cuz we might need it while talking to this
        // station.  We need it to implement Reply/Ack protocol.

// Note that msg_id has already been truncated by this point.
// orig_msg_id contains the full REPLY-ACK text.

//fprintf(stderr, "store_most_recent_ack()\n");
        store_most_recent_ack(call,msg_id);

        // fprintf(stderr,"found Msg w line to me: |%s| |%s|\n",message,msg_id);
        last_ack_sent = msg_data_add(addr,
                            call,
                            message,
                            msg_id,
                            MESSAGE_MESSAGE,
                            port,
                            &record); // id_fixed

        // Here we need to know if it is a new message or an old.
        // If we've already received it, we don't want to kick off
        // the alerts or pop up the Send Message dialog again.  If
        // last_ack_sent == (time_t)0, then it is a new message.
        //
        if (last_ack_sent == (time_t)0l && record == -1l) { // Msg we've never received before

            new_message_data += 1;

            // Note that the check_popup_window() function will
            // re-create a Send Message dialog if one doesn't exist
            // for this QSO.  Only call it for the first message
            // line or the first ack, not for any repeats.
            //
            (void)check_popup_window(call, 2);  // Calls update_messages()

            //update_messages(1); // Force an update


        }

        // Try to only send an ack out once per 30 seconds at the
        // fastest.
//WE7U
// Does this 30-second check work?
        //
        if ( (last_ack_sent != (time_t)-1l)   // Not an error
                && (last_ack_sent + 30 ) < sec_now()
                && !satellite_ack_mode // Disable separate ack's for satellite work
                && port != -1 ) {   // Not from a log file

            char path1[MAX_LINE_SIZE+1];
            path1[0] = '\0';

            // Update the last_ack_sent field for the message
            msg_update_ack_stamp(record);

            pad_callsign(from_call,call);         /* ack the message */


            // Attempt to snag a custom path out of the Send Message
            // dialog, if set.  If not set, path will contain '\0';
            get_send_message_path(call, path1, MAX_LINE_SIZE+1);
//fprintf(stderr,"Path: %s\n", path);


            // In this case we want to send orig_msg_id back, not
            // the (possibly) truncated msg_id.  This is per Bob B's
            // Reply/Ack spec, sent to xastir-dev on Nov 14, 2001.
            snprintf(ack, sizeof(ack), ":%s:ack%s",from_call,orig_msg_id);

//WE7U
// Need to figure out the reverse path for this one instead of
// passing a NULL for the path?  Probably not, as auto-calculation
// of paths isn't a good idea.
//
// What we need to do here is check whether we have a custom path
// set for this QSO.  If so, pass that path along as the transmit
// path.  messages.h:Message_Window struct has the send_message_path
// variable in it.  If a Message_Window still exists for this QSO
// then we can snag the user-entered path from there.  If the struct
// has already been destroyed then we have nowhere to snag the
// custom path from and have to rely on the default paths in each
// interface properties dialog instead.  Then again, we _could_ snag
// the path out of the last received message in the message database
// for that case.  Might be better to disable the Close button, or
// warn the user that the custom path will be lost if they close the
// Send Message dialog.


            // Send out the immediate ACK
            if (path1[0] == '\0')
                transmit_message_data(call,ack,NULL);
            else
                transmit_message_data(call,ack,path1);


            if (record != -1l) { // Msg we've received before

                // It's a message that we've received before,
                // consider sending an extra ACK in about 30 seconds
                // to try to get it to the remote station.  Perhaps
                // another one in 60 seconds as well.

                if (path1[0] == '\0') {
                    transmit_message_data_delayed(call,ack,NULL,sec_now()+30);
                    transmit_message_data_delayed(call,ack,NULL,sec_now()+60);
                    transmit_message_data_delayed(call,ack,NULL,sec_now()+120);
                }
                else {
                    transmit_message_data_delayed(call,ack,path1,sec_now()+30);
                    transmit_message_data_delayed(call,ack,path1,sec_now()+60);
                    transmit_message_data_delayed(call,ack,path1,sec_now()+120);
                }
            }

/*
 * TODO
            if (auto_reply == 1) {

                snprintf(ipacket_message,
                    sizeof(ipacket_message), "AA:%s", auto_reply_message);

                if (!from_my_call) // Check SSID also
                    output_message(my_callsign, call, ipacket_message, "");
            }
*/
        }


        done = 1;
    }

    //--------------------------------------------------------------------------
    if (!done && strlen(msg_id) == 0 && to_my_call) {   // Message for me (including SSID check)
                                                        // but without message-ID.
        // These should appear in a Send Message dialog and should
        // NOT get ack'ed.  Kenwood radios send this message type as
        // an auto-answer or a buffer-full message.  They look
        // something like:
        //
        //      :WE7U-13 :Not at keyboard.
        //

        time_t last_ack_sent;
        long record;


        if (len > 2
                && message[0] == '?'
                && port != -1   // Not from a log file
                && to_my_call) { // directed query (check SSID also)
            // Smallest query known is "?WX".
            done = process_directed_query(call,path,message+1,port);
        }

        // fprintf(stderr,"found Msg w line to me: |%s| |%s|\n",message,msg_id);
        last_ack_sent = msg_data_add(addr,
                            call,
                            message,
                            msg_id,
                            MESSAGE_MESSAGE,
                            port,
                            &record); // id_fixed

        // Here we need to know if it is a new message or an old.
        // If we've already received it, we don't want to kick off
        // the alerts or pop up the Send Message dialog again.  If
        // last_ack_sent == (time_t)0, then it is a new message.
        //
        if (last_ack_sent == (time_t)0l && record == -1l) { // Msg we've never received before

            new_message_data += 1;

            // Note that the check_popup_window() function will
            // re-create a Send Message dialog if one doesn't exist
            // for this QSO.  Only call it for the first message
            // line or the first ack, not for any repeats.
            //
//fprintf(stderr,"***check_popup_window 1\n");
            (void)check_popup_window(call, 2);  // Calls update_messages()

            //update_messages(1); // Force an update

        }

        // Update the last_ack_sent field for the message, even
        // though we won't be sending an ack in response.
        msg_update_ack_stamp(record);


        done = 1;
    }

    //--------------------------------------------------------------------------
    if (!done
            && ( (strncmp(addr,"NWS-",4) == 0)          // NWS weather alert
              || (strncmp(addr,"NWS_",4) == 0) ) ) {    // NWS weather alert compressed

        // could have sort of line number
        //fprintf(stderr,"found NWS: |%s| |%s| |%s|\n",addr,message,msg_id);

        (void)alert_data_add(addr,
            call,
            message,
            msg_id,
            MESSAGE_NWS,
            port);

        done = 1;

/*
 * TODO - add igate support
        if (operate_as_an_igate>1
                && from==DATA_VIA_NET
                && !from_my_call // Check SSID also
                && port != -1) { // Not from a log file
            char short_path[100];

            shorten_path(path,short_path,sizeof(short_path));

            snprintf(ipacket_message,
                sizeof(ipacket_message),
                "}%s>%s,TCPIP,%s*::%s:%s",
                call,
                short_path,
                my_callsign,
                addr9,
                message);

            output_nws_igate_rf(call,
                path,
                ipacket_message,
                port,
                third_party);
        }
*/
    }

    //--------------------------------------------------------------------------
    if (!done && strncmp(addr,"SKY",3) == 0) {  // NWS weather alert additional info

        // could have sort of line number
        //fprintf(stderr,"found SKY: |%s| |%s| |%s|\n",addr,message,msg_id);

		// We don't wish to record these in memory.  They cause an infinite
		// loop in the current code and a massive memory leak.
		return(1);  // Tell the calling program that the packet was ok so
		            // that it doesn't add it with data_add() itself!


        done = 1;
/*
 * TODO - add igate support
        if (operate_as_an_igate>1
                && from==DATA_VIA_NET
                && !from_my_call    // Check SSID also
                && port != -1) { // Not from a log file
            char short_path[100];

            shorten_path(path,short_path,sizeof(short_path));

            snprintf(ipacket_message,
                sizeof(ipacket_message),
                "}%s>%s,TCPIP,%s*::%s:%s",
                call,
                short_path,
                my_callsign,
                addr9,
                message);

            output_nws_igate_rf(call,
                path,
                ipacket_message,
                port,
                third_party);
        }
*/
    }

    //--------------------------------------------------------------------------
    if (!done && strlen(msg_id) > 0) {  // Other message with linenumber.  This
                                        // is either a message for someone else
                                        // or a message for another one of my
                                        // SSID's.
        long record_out;
        time_t last_ack_sent;
        char message_plus_note[MAX_MESSAGE_LENGTH + 30];


        if (to_my_base_call && !from_my_call) {
            // Special case:  We saw a message w/msg_id that was to
            // one of our other SSID's, but it was not from
            // ourselves.  That last bit (!from_my_call) is
            // important in the case where we're working an event
            // with several stations using the same callsign.
            //
            // Store as if it came to my callsign, with a zeroed-out
            // msg_id so we can't try to ack it.  We also need some
            // other indication in the "Send Message" dialog as to
            // what's happening.  Perhaps add the original callsign
            // to the message itself in a note at the start?
            //
            snprintf(message_plus_note,
                sizeof(message_plus_note),
                "(Sent to:%s) %s",
                addr,
                message);
            last_ack_sent = msg_data_add(_aprs_mycall,
                call,
                message_plus_note,
                "",
                MESSAGE_MESSAGE,
                port,
                &record_out);
        }
        else {  // Normal case, messaging between other people
            last_ack_sent = msg_data_add(addr,
                call,
                message,
                msg_id,
                MESSAGE_MESSAGE,
                port,
                &record_out);
        }

        new_message_data += look_for_open_group_data(addr);

        // Note that the check_popup_window() function will
        // re-create a Send Message dialog if one doesn't exist for
        // this QSO.  Only call it for the first message line or the
        // first ack, not for any repeats.
        //
        if (last_ack_sent == (time_t)0l && record_out == -1l) { // Msg we've never received before

            // Callsign check here also checks SSID for exact match
// We need to do an SSID-non-specific check here so that we can pick
// up messages intended for other stations of ours.

            if ((to_my_base_call && check_popup_window(call, 2) != -1)
                    || check_popup_window(call, 0) != -1
                    || check_popup_window(addr, 1) != -1) {
                update_messages(1); // Force an update
            }
        }

        /* Now if I have Igate on and I allow to retransmit station data           */
        /* check if this message is to a person I have heard on my TNC within an X */
        /* time frame. If if is a station I heard and all the conditions are ok    */
        /* spit the message out on the TNC -FG                                     */
  /*
   * TODO - add igate support
        if (operate_as_an_igate>1
                && last_ack_sent != (time_t)-1l
                && from==DATA_VIA_NET
                && !from_my_call        // Check SSID also
                && !to_my_call          // Check SSID also
                && port != -1) {    // Not from a log file
            char short_path[100];

            shorten_path(path,short_path,sizeof(short_path));
            snprintf(ipacket_message,
                sizeof(ipacket_message),
                "}%s>%s,TCPIP,%s*::%s:%s",
                call,
                short_path,
                my_callsign,
                addr9,
                message_plus_acks);


            output_igate_rf(call,
                addr,
                path,
                ipacket_message,
                port,
                third_party,
                NULL);

            igate_msgs_tx++;
        }
*/
        done = 1;
    }

    //--------------------------------------------------------------------------

    if (!done) {                                   // message without line number
        long record_out;
        time_t last_ack_sent;


        last_ack_sent = msg_data_add(addr,
            call,
            message,
            "",
            MESSAGE_MESSAGE,
            port,
            &record_out);

        new_message_data++;      // ??????

        // Note that the check_popup_window() function will
        // re-create a Send Message dialog if one doesn't exist for
        // this QSO.  Only call it for the first message line or the
        // first ack, not for any repeats.
        //
        if (last_ack_sent == (time_t)0l && record_out == -1l) { // Msg we've never received before
            if (check_popup_window(addr, 1) != -1) {
                //update_messages(1); // Force an update
            }
        }

        // Could be response to a query.  Popup a messsage.

// Check addr for my_call and !third_party, then check later in the
// packet for my_call if it is a third_party message?  Depends on
// what the packet looks like by this point.
        if ( last_ack_sent != (time_t)-1l
                && (message[0] != '?')
                && to_my_call ) { // Check SSID also

            // We no longer wish to have both popups and the Send
            // Group Message dialogs come up for every query
            // response, so we use popup_message() here instead of
            // popup_message_always() so that by default we'll see
            // the below message in STDERR.  If --with-errorpopups
            // has been configured in, we'll get a popup as well.
            // Send Group Message dialogs work well for multi-line
            // query responses, so we'll leave it that way.
            //
// TODO
//            popup_message(langcode("POPEM00018"),message);

            // Check for Reply/Ack.  APRS+ sends an AA: response back
            // for auto-reply, with an embedded free-ride Ack.
            if (strlen(ack_string) != 0) {  // Have an extra ack to deal with

                clear_acked_message(call,addr,ack_string);  // got an ACK for me

                msg_record_ack(call,addr,ack_string,0,0);   // Record the ack for this message
            }
        }

        // done = 1;
    }

    //--------------------------------------------------------------------------

    if (ok)
    {
        (void)data_add(STATION_CALL_DATA,
            call,
            path,
            message,
            port,
            NULL,
            third_party,
            0,  // Not a packet from my station
            0); // Not my object/item
        const gchar *msg = g_strdup_printf("%s\n%s", call,message);

        MACRO_BANNER_SHOW_INFO(_window, msg);
    }


    return(ok);
}



/***********************************************************************/
/****************************APRS Mesesage END**************************/
/***********************************************************************/













/* APRS INET CONTROL */
void aprs_inet_destroy()
{
	if(_aprs_inet_beacon_timer>0) g_source_remove(_aprs_inet_beacon_timer);
    if(iconn_aprs)
    {
		if(g_mutex_trylock (_write_buffer[APRS_PORT_INET].write_lock))
		{
			gnome_vfs_inet_connection_destroy(iconn_aprs, NULL);
			g_mutex_unlock(_write_buffer[APRS_PORT_INET].write_lock);
		}
		MACRO_BANNER_SHOW_INFO(_window, _("APRS server disconnected!"));
       	fprintf(stderr,"APRS server disconnected!\n");

    }
    iconn_aprs = NULL;
}
void aprs_inet_init()
{
    if(iconn_aprs)
    {
    	aprs_inet_destroy();
    }
    aprs_inet_timer_init();
    connect_aprs_server();
}

gboolean is_aprs_inet_connected()
{
    if(iconn_aprs)
    	return TRUE;
    else
    	return FALSE;
}
/* APRS INET END */

/* APRS TNC CONROL */
void aprs_tnc_destroy()
{
	if(_aprs_tty_beacon_timer>0) g_source_remove(_aprs_tty_beacon_timer);
    if(port_data.device_name[0] != 0 )
    {
    	aprs_tty_disconnect();
    	MACRO_BANNER_SHOW_INFO(_window, _("APRS TNC disconnected!"));
       	fprintf(stderr,"APRS TNC disconnected!\n");

    }
    port_data.device_name[0] = 0;
}

void aprs_tnc_init()
{
   	aprs_tnc_destroy();
    aprs_tnc_timer_init();
    aprs_tty_connect();
    MACRO_BANNER_SHOW_INFO(_window, _("APRS TNC connected!"));
}
gboolean is_aprs_tnc_connected()
{
	if(port_data.device_name[0] != 0)
		return TRUE;
	else
		return FALSE;
}

/* APRS TNC END */
/* APRS DISPLAY add by nestor */
typedef struct _SInfo SInfo;
struct _SInfo {
    gdouble distance;
    gchar course_deg[8];
    glong sec;
    AprsDataRow *p_station;
 } ;

static gint
compare_station_distance (gconstpointer  a,
		   gconstpointer  b)
{
  const SInfo *aa = a;
  const SInfo *bb = b;
  if (aa->distance > bb->distance)
    return 1;
  else if (aa->distance == bb->distance)
    return 0;
  else
    return -1;
}
void convert_temp_f_to_c(gchar * f, gchar ** c)
{
	*c = g_strdup("        ");

	gdouble df = 0.0;
	gdouble dc = 0.0;

	// Convert fahrenheit to fahrenheit (double)
	df = g_ascii_strtod ( f, (gchar**)(f + strlen(f)));

	// Convert ff to fc
	dc = 5*((df - 32.0)/9);

	// Convert fc to celsius
	snprintf(*c, 8, "%0.1f°C", dc);
}

void
message_edit_dialog(GtkWindow *parent,gchar *callsign)
{

    GtkWidget *label,*msg_entry,*callsign_entry;
    GtkWidget *dialog;
    GtkWidget *pannable;
    GtkTable *table;
    GtkWidget *route_button;
    HildonTouchSelector *route_selector;
    gchar *callsign_label = NULL, *message_label = NULL;


    enum {
        RESP_OK,
    };
    gint selected;
    if(callsign)
    {

    	callsign_label = g_strdup(callsign);
    }
    else
    {
    	callsign_label = g_strdup("");
    }
	message_label = g_strdup_printf("%s", _aprs_msg_txt);


    dialog = gtk_dialog_new_with_buttons(
                 _("Send Messgae"), parent, GTK_DIALOG_MODAL, NULL);
    gtk_dialog_add_button(GTK_DIALOG(dialog), GTK_STOCK_OK, RESP_OK);
    table = GTK_TABLE(gtk_table_new(3, 4, TRUE));
    pannable = hildon_pannable_area_new();
    gtk_widget_set_size_request(pannable, -1, 300);
    hildon_pannable_area_add_with_viewport(HILDON_PANNABLE_AREA(pannable),
                                           GTK_WIDGET(table));
    gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), pannable, TRUE, TRUE, 0);

    /* Route */
    label = gtk_label_new(_("Route"));
    gtk_table_attach(table, label, 0, 1, 0, 1, GTK_FILL | GTK_EXPAND, 0, 2, 4);
    route_selector = HILDON_TOUCH_SELECTOR(hildon_touch_selector_new_text());

    selected = 0;
    route_selector = HILDON_TOUCH_SELECTOR (hildon_touch_selector_new_text());
    hildon_touch_selector_append_text(route_selector, _("Internet"));
    hildon_touch_selector_append_text(route_selector, _("TNC"));
    route_button = g_object_new(HILDON_TYPE_PICKER_BUTTON,
                                 "size", HILDON_SIZE_FINGER_HEIGHT,
                                 "title", _("VIA"),
                                 "touch-selector", route_selector,
                                 NULL);
    gtk_table_attach(table, route_button, 1, 4, 0, 1, GTK_FILL | GTK_EXPAND, 0, 2, 4);
	if(is_aprs_inet_connected())
		hildon_touch_selector_set_active(route_selector, 0, selected);
	else
		hildon_touch_selector_set_active(route_selector, 1, selected);


     label = gtk_label_new(_("Callsign"));
     gtk_table_attach(table, label, 0, 1, 1, 2, GTK_FILL | GTK_EXPAND, 0, 2, 4);
     callsign_entry = hildon_entry_new(HILDON_SIZE_FINGER_HEIGHT);
     gtk_table_attach(table, callsign_entry, 1, 4, 1, 2, GTK_FILL | GTK_EXPAND, 0, 2, 4);
     hildon_entry_set_text(HILDON_ENTRY(callsign_entry), callsign_label);

    /* Msg */
    label = gtk_label_new(_("Message"));
    gtk_table_attach(table, label, 0, 1, 2, 3, GTK_FILL | GTK_EXPAND, 0, 2, 4);
    msg_entry = hildon_entry_new(HILDON_SIZE_FINGER_HEIGHT);
    gtk_table_attach(table, msg_entry, 1, 4, 2, 3, GTK_FILL | GTK_EXPAND, 0, 2, 4);
    hildon_entry_set_text(HILDON_ENTRY(msg_entry), message_label);

    g_free(callsign_label);
    g_free(message_label);



    gtk_widget_show_all(dialog);
    if (gtk_dialog_run(GTK_DIALOG(dialog)) == RESP_OK)
    {
    	const gchar *str;
    	gchar *title,*buf;
        gchar msg[100];
        selected=hildon_touch_selector_get_active(route_selector, 0);
        str = gtk_entry_get_text(GTK_ENTRY(callsign_entry));
        title=g_strdup(str);
        str= gtk_entry_get_text(GTK_ENTRY(msg_entry));
        buf=g_strdup(str);
        snprintf(msg,sizeof(msg),"%s\r\n",buf);
        if(selected==0)
        {
        	if(is_aprs_inet_connected())
        		output_my_msg_data(APRS_PORT_INET,title,msg);
        	else
        	{
        		MACRO_BANNER_SHOW_INFO(_window, _("APRS MSG NOT SENT\n"));
        	}
        }
        else
        {

        	if(is_aprs_tnc_connected())
        		output_my_msg_data(APRS_PORT_TTY,title,msg);
        	else
        	{
        		MACRO_BANNER_SHOW_INFO(_window, _("APRS MSG NOT SENT\n"));
        	}
        }
        g_free(title);
        g_free(buf);

    }


    gtk_widget_destroy(dialog);
    /*OLD MSG send code bak
     	gchar *callsign_label = NULL, *message_label = NULL;

    GtkWidget *dialog;
    GtkWidget *table;
    GtkWidget *label;
    GtkWidget *txt_label;
    GtkWidget *txt_desc;
    GtkWidget *txt_scroll;
    GtkTextBuffer *desc_txt;
    GtkTextIter begin, end;
    HildonTouchSelector *route_selector;
    gint selected;

    if(callsign)
    {

    	callsign_label = g_strdup(callsign);
    	message_label = g_strdup_printf("Hello! Regards from %s\r\n", _aprs_mycall);
    }
    else
    {
    	callsign_label = g_strdup("");
    	message_label = g_strdup_printf("Hello! Regards from %s\r\n", _aprs_mycall);
    }


    dialog = gtk_dialog_new_with_buttons(_("Send Messgae (Internet)"),
            GTK_WINDOW(parent), GTK_DIALOG_MODAL,
            GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
            NULL);


    gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox),
            table = gtk_table_new(6, 4, FALSE), TRUE, TRUE, 0);


     label = gtk_label_new(_("Route"));
     gtk_table_attach(table, label, 0, 1, 0, 1, GTK_FILL | GTK_EXPAND, 0, 2, 4);
     method_selector = HILDON_TOUCH_SELECTOR(hildon_touch_selector_new_text());

     selected = _aprs_tnc_method;
     route_selector = HILDON_TOUCH_SELECTOR (hildon_touch_selector_new_text());
     hildon_touch_selector_append_text(route_selector, _("Internet"));
     hildon_touch_selector_append_text(route_selector, _("Bluetooth TNC"));
     method_button = g_object_new(HILDON_TYPE_PICKER_BUTTON,
                                  "size", HILDON_SIZE_FINGER_HEIGHT,
                                  "title", _("TNC Method"),
                                  "touch-selector", route_selector,
                                  NULL);
     gtk_table_attach(table, method_button, 1, 4, 0, 1, GTK_FILL | GTK_EXPAND, 0, 2, 4);
     hildon_touch_selector_set_active(method_selector, 0, selected);



    gtk_table_attach(GTK_TABLE(table),
            label = gtk_label_new(_("Callsign")),
            0, 1, 0, 1, GTK_FILL, 0, 2, 4);
    gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
    gtk_table_attach(GTK_TABLE(table),
            txt_label = gtk_entry_new(),
            1, 2, 0, 1, GTK_EXPAND | GTK_FILL, 0, 2, 4);


    gtk_table_attach(GTK_TABLE(table),
            label = gtk_label_new(_("Message")),
            0, 1, 1, 2, GTK_FILL, 0, 2, 4);
    gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);

    txt_scroll = gtk_scrolled_window_new(NULL, NULL);
    gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(txt_scroll),
                                   GTK_SHADOW_IN);
    gtk_table_attach(GTK_TABLE(table),
            txt_scroll,
            1, 2, 1, 2, GTK_EXPAND | GTK_FILL, 0, 2, 4);

    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(txt_scroll),
                                 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);

    txt_desc = gtk_text_view_new();
    gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(txt_desc), GTK_WRAP_WORD);

    gtk_container_add(GTK_CONTAINER(txt_scroll), txt_desc);
    gtk_widget_set_size_request(GTK_WIDGET(txt_scroll), 400, 60);

    desc_txt = gtk_text_view_get_buffer(GTK_TEXT_VIEW(txt_desc));

    gtk_entry_set_text(GTK_ENTRY(txt_label), callsign_label);

    gtk_text_buffer_set_text(desc_txt, message_label, -1);


    g_free(callsign_label);
    callsign_label = NULL;
    g_free(message_label);
    message_label = NULL;

    gtk_widget_show_all(dialog);

    while(GTK_RESPONSE_ACCEPT == gtk_dialog_run(GTK_DIALOG(dialog)))
    {
        if(strlen(gtk_entry_get_text(GTK_ENTRY(txt_label))))
        	callsign_label = g_strdup(gtk_entry_get_text(GTK_ENTRY(txt_label)));
        else
        {
            popup_error(dialog, _("Please specify a callsign."));
            continue;
        }

        gtk_text_buffer_get_iter_at_offset(desc_txt, &begin,0 );
        gtk_text_buffer_get_end_iter (desc_txt, &end);
        message_label = gtk_text_buffer_get_text(desc_txt, &begin, &end, TRUE);
        if(strlen(message_label)>60 || strlen(message_label)<3)
        {
            popup_error(dialog, _("Message too long or short."));
            continue;
        }
        if(strlen(callsign_label)>9)
        {
            popup_error(dialog, _("Callsign too long"));
            continue;
        }
        output_my_msg_data(APRS_PORT_INET,callsign_label,message_label);

        break;
    }
    g_object_unref (desc_txt);
    gtk_widget_hide(dialog);
     */


}



static void
run_aprs_detail_dialog(GtkWindow *parent,AprsDataRow *p_station)
{
	GtkWidget *label;
	GtkWidget *dialog;
	//GtkWidget *pannable;
	//GtkTable *table;
	gchar * content = NULL;

	gchar content_buf[100],content_buf2[100],content_buf3[100];
	gint response;
    GtkBox *vbox;

	gchar distance[15];
	gchar lat[15], lon[15];
	gchar course_deg[9];
	course_deg[0] = '\0';
	distance[0] = '\0';
	lat[0] = '\0';
	lon[0] = '\0';

	if (p_station->coord_lat != 0 || p_station->coord_lon != 0) {
		gdouble d_lat = convert_lat_l2d(p_station->coord_lat);
		gdouble d_lon = convert_lon_l2d(p_station->coord_lon);

		format_lat_lon(d_lat, d_lon, lat, lon);

		gfloat dist = (float) calculate_distance(_gps.lat, _gps.lon, d_lat,
				d_lon);

		snprintf(distance, sizeof(distance), "%.01f %s", dist
				* UNITS_CONVERT[_units], UNITS_ENUM_TEXT[_units]);

		snprintf(course_deg, sizeof(course_deg), "%.01f°", calculate_bearing(
				_gps.lat, _gps.lon, d_lat, d_lon));
	}

	/* Title: Callsign Distance Course Packets*/
	snprintf(content_buf, sizeof(content_buf),"%s %s %s (%u)",p_station->call_sign,distance,course_deg,p_station->num_packets);
	dialog = gtk_dialog_new_with_buttons(content_buf,  parent,
			GTK_DIALOG_MODAL, NULL);
    vbox = GTK_BOX(GTK_DIALOG(dialog)->vbox);


    /* Time: Lat,lon,Alt(m),Speed */
	if (p_station->sec_heard) {
		strftime(content_buf, sizeof(content_buf), "%x %X", localtime(&p_station->sec_heard));
	} else {
		snprintf(content_buf, sizeof(content_buf), " ");
	}
	if (_units == UNITS_NM)
		snprintf(content_buf2, sizeof(content_buf2), "%.01f nmph", atof(
				p_station->speed));
	else if (_units == UNITS_KM)
		snprintf(content_buf2, sizeof(content_buf2), "%.01f kph", atof(
				p_station->speed) * 1.852);
	else if (_units == UNITS_MI)
		snprintf(content_buf2, sizeof(content_buf2), "%.01f mph", atof(
				p_station->speed) * 1.1508);

	snprintf(content_buf3, sizeof(content_buf3),"%s %s %s %s ",lat,lon,p_station->altitude,content_buf2);


	label = hildon_button_new_with_text
        (HILDON_SIZE_FINGER_HEIGHT, HILDON_BUTTON_ARRANGEMENT_VERTICAL,
        		content_buf, content_buf3);
    gtk_button_set_alignment(GTK_BUTTON(label), 0.0, 0.5);
    gtk_box_pack_start(vbox, label, FALSE, TRUE, 0);


	/* Comment */
	if (p_station->comment_data && p_station->comment_data->text_ptr) {
		content = g_strdup(p_station->comment_data->text_ptr);
	} else {
		content = g_strdup(" ");
	}
	label = hildon_button_new_with_text
        (HILDON_SIZE_FINGER_HEIGHT, HILDON_BUTTON_ARRANGEMENT_VERTICAL,
        		"Comment", content);
    gtk_button_set_alignment(GTK_BUTTON(label), 0.0, 0.5);
    gtk_box_pack_start(vbox, label, FALSE, TRUE, 0);
	g_free(content);

	/* Status */
	if (p_station->status_data && p_station->status_data->text_ptr) {
		content = g_strdup(p_station->status_data->text_ptr);
	} else {
		content = g_strdup(" ");
	}
	label = hildon_button_new_with_text
        (HILDON_SIZE_FINGER_HEIGHT, HILDON_BUTTON_ARRANGEMENT_VERTICAL,
        		"Status", content);
    gtk_button_set_alignment(GTK_BUTTON(label), 0.0, 0.5);
    gtk_box_pack_start(vbox, label, FALSE, TRUE, 0);
	g_free(content);

	/* Path */
	if (p_station->node_path_ptr) {
		content = g_strdup(p_station->node_path_ptr);
	} else {
		content = g_strdup(" ");
	}
	label = hildon_button_new_with_text
        (HILDON_SIZE_FINGER_HEIGHT, HILDON_BUTTON_ARRANGEMENT_VERTICAL,
        		"Path", content);
    gtk_button_set_alignment(GTK_BUTTON(label), 0.0, 0.5);
    gtk_box_pack_start(vbox, label, FALSE, TRUE, 0);
	g_free(content);

	/* Weather */
	if(p_station->weather_data )
	{

		/*Temprature*/
	    if(p_station->weather_data && p_station->weather_data->wx_temp)
	    {
	    	convert_temp_f_to_c(p_station->weather_data->wx_temp, &content);
	    }
	    else
	    {
	    	content = g_strdup(" ");
	    }
	    /*Course*/
	    gchar course[10];

	    snprintf(course, sizeof(course), "%0.f°",
	    		g_ascii_strtod (p_station->weather_data->wx_course, (gchar **)(p_station->weather_data->wx_course + strlen(p_station->weather_data->wx_course)))
	    		);
	    /*Speed */
	    gchar speed[15];
	    snprintf(speed, sizeof(speed), "%0.f->%0.f MPH",
	    		g_ascii_strtod (p_station->weather_data->wx_speed, (gchar **)(p_station->weather_data->wx_speed + strlen(p_station->weather_data->wx_speed))),
	    		g_ascii_strtod (p_station->weather_data->wx_gust, (gchar **)(p_station->weather_data->wx_gust + strlen(p_station->weather_data->wx_gust)))
	    		);
	    /*Rain */
	    gchar rain_ph[17];
	    gchar rain_total[17];
	    if(p_station->weather_data->wx_rain)
	    {
		    snprintf(rain_ph, 16, "%0.f \"/hr",
		    		g_ascii_strtod (p_station->weather_data->wx_rain, (gchar **)(p_station->weather_data->wx_rain + strlen(p_station->weather_data->wx_rain)))
		    		);
	    }
	    else
	    {
	    	snprintf(rain_ph, 1, " ");
	    }
	    if(p_station->weather_data->wx_rain_total)
	    {
		    snprintf(rain_total, 16, "%0.f \"",
		    		g_ascii_strtod (p_station->weather_data->wx_rain_total,(gchar **)( p_station->weather_data->wx_rain_total + strlen(p_station->weather_data->wx_rain_total)))
		    		);
	    }
	    else
	    {
	    	snprintf(rain_total, 1, " ");
	    }


		snprintf(content_buf2, sizeof(content_buf),"Weather (%s)",content);
		snprintf(content_buf3, sizeof(content_buf),"Wind %s %s Rain %s %s",speed,course,rain_ph,rain_total);
		label = hildon_button_new_with_text
	        (HILDON_SIZE_FINGER_HEIGHT, HILDON_BUTTON_ARRANGEMENT_VERTICAL,
	        		content_buf2, content_buf3);
	    gtk_button_set_alignment(GTK_BUTTON(label), 0.0, 0.5);
	    gtk_box_pack_start(vbox, label, FALSE, TRUE, 0);
		g_free(content);


	}




    gtk_widget_show_all(dialog);
	response = gtk_dialog_run(GTK_DIALOG(dialog));

	gtk_widget_destroy(dialog);
}

void
map_menu_aprs_station_list()
{
    GtkWidget *dialog,*dialog2;
    GtkWindow *parent;

    MapController *controller;
    MapDialog *dlg,*dlg2;
    gint response,response2;
    gint stationcount;
    enum {
    	GO_TO_LOC,
    	GO_TO_NAVI,
    	GO_TO_DETAIL,
    	GO_TO_MESSAGE,
    };

    GList *station_list = NULL;


    AprsDataRow *p_station = n_first;
    curr_sec = sec_now();
    stationcount=0;
    while ( (p_station) != NULL)
    {
 		SInfo *station = g_slice_new(SInfo);
    	stationcount++;
		station->course_deg[0]=0;
	    if(p_station->coord_lat != 0 && p_station->coord_lon != 0)
	    {
	    	station->distance = distance_from_my_station(p_station->call_sign, station->course_deg, sizeof(station->course_deg));
    	}
    	else
    	{
    		station->distance = -1;
    	}
	    station->sec = 0;
	    if(p_station->sec_heard)
	    {
	    	station->sec =  curr_sec - p_station->sec_heard;
	    }


		 station->p_station = p_station;
		 //fprintf(stderr,"%s %s\n",p_station->call_sign,station->course_deg);
		 station_list = g_list_prepend(station_list, station);
		 p_station=p_station->n_next;
    }

    station_list = g_list_sort (station_list, compare_station_distance);

    /*View*/
    gchar * title = g_strdup_printf("ARPS Stations (%i)",stationcount);
    controller = map_controller_get_instance();
    dialog = map_dialog_new(title,
                            map_controller_get_main_window(controller),
                            TRUE);
    dlg = (MapDialog *)dialog;


    GList *p_list;
    p_list=station_list;
    stationcount=0;
    while ( p_list != NULL)
    {

    	SInfo *station = (SInfo *)p_list->data;

    	gchar * comment = NULL;
    	gchar * callsign = g_strdup(station->p_station->call_sign);
    	gchar * formatted_distance = NULL;
    	gchar * times;
    	stationcount++;
		if(station->distance >= 0)
		{
			if(_units == UNITS_KM)
				formatted_distance = g_strdup_printf("%.01f km", station->distance);
			else if(_units == UNITS_MI)
				formatted_distance = g_strdup_printf("%.01f miles", station->distance);
			else if(_units == UNITS_NM)
				formatted_distance = g_strdup_printf("%.01f nm", station->distance);
		}
		else
			formatted_distance = g_strdup_printf(" ");

    	if(station->p_station->comment_data) comment = g_strdup(station->p_station->comment_data->text_ptr);
    	else comment = g_strdup(" ");




    	if(station->sec < 10)
    		times = g_strdup_printf("1s ago");
    	else if(station->sec < 30)
			times = g_strdup_printf("10s ago");
    	else if(station->sec<60)
			times = g_strdup_printf("30s ago");
    	else if(station->sec<60*10)
			times = g_strdup_printf("1m ago");
    	else if(station->sec<60*30)
			times = g_strdup_printf("10m ago");
    	else if(station->sec<60*60)
			times = g_strdup_printf("30m ago");
    	else if(station->sec<60*60*5)
    		times = g_strdup_printf("1h ago");
    	else if(station->sec<60*60*10)
    		times = g_strdup_printf("5h ago");
    	else if(station->sec<60*60*24)
    		times = g_strdup_printf("10h ago");
    	else
    		times = g_strdup_printf("1d ago");




    	/*display rig type */
    	gchar rig_type[10];
    	if(strstr(comment,"TH-D72"))
    		strcpy(rig_type,"D72");
    	else if(strstr(comment,"TH-D7"))
    		strcpy(rig_type,"D7");
    	else if(strstr(comment,"TM-D710"))
    		strcpy(rig_type,"D710");
    	else if(strstr(comment,"TM-D700"))
    	    strcpy(rig_type,"D700");
    	else if(strstr(comment,"VX-8G"))
    	    strcpy(rig_type,"8G");
    	else if(strstr(comment,"VX-8"))
    	    strcpy(rig_type,"8R");
    	else if(strstr(comment,"FTM-350"))
    	    strcpy(rig_type,"350");
    	else
			strcpy(rig_type," ");



    	/*end*/


    	//printf( "%*s ",len,p);  BG2STH-12 11111.22 km
    	//gchar * stationbtn = g_strdup_printf("%*s %*s %*s %*s",9,callsign,11,formatted_distance,4,station->course_deg,7,times);
    	gchar * stationbtn = g_strdup_printf("%s %s %s %s %s",callsign,formatted_distance,station->course_deg,times,rig_type);
    	//fprintf(stderr,"Create:%i\n",stationcount);
    	map_dialog_create_button_align(dlg, stationbtn, stationcount);
        g_free(comment);
        g_free(callsign);
        g_free(formatted_distance);
        g_free(times);
    	p_list=p_list->next;
    } // End of while loop




    response = gtk_dialog_run(GTK_DIALOG(dialog));
    gtk_widget_destroy(dialog);
    //fprintf(stderr,"choice:%i\n",response);
    //Find Which Station Selected
    stationcount=0;
    GList *curr;
    for(curr = station_list; curr; curr = curr->next)
    {

    	stationcount ++;
   	   //fprintf(stderr,"choice:%i,SC:%i\n",response,stationcount);
    	if(stationcount == response)
    	{
        	MapPoint unit;
        	SInfo *station = (SInfo *)curr->data;
        	gchar * title=g_strdup_printf("%s",station->p_station->call_sign);
        	gdouble lat,lon;
       		lat = convert_lat_l2d(station->p_station->coord_lat) + station->p_station->offsetlat;
       		lon = convert_lon_l2d(station->p_station->coord_lon) + station->p_station->offsetlon;


        	dialog2 = map_dialog_new(title,
        	                            map_controller_get_main_window(controller),
        	                            TRUE);
       	    parent = GTK_WINDOW(dialog2);
        	dlg2 = (MapDialog *)dialog2;
        	map_dialog_create_button(dlg2, _("Go to..."), GO_TO_LOC);
        	map_dialog_create_button(dlg2, _("Navi to..."), GO_TO_NAVI);
         	map_dialog_create_button(dlg2, _("Detail..."), GO_TO_DETAIL);
         	if(is_aprs_inet_connected() || is_aprs_tnc_connected())
         		map_dialog_create_button(dlg2, _("Message to..."), GO_TO_MESSAGE);
       	response2 = gtk_dialog_run(GTK_DIALOG(dialog2));
            switch (response2) {
            case GO_TO_LOC:
            	latlon2unit(lat, lon, unit.x, unit.y);
            	map_controller_disable_auto_center(controller);
            	map_controller_set_center(controller, unit, -1);
                fprintf(stderr,"GO_TO_LOC\n");
            	break;
            case GO_TO_NAVI:
            	fprintf(stderr,"GO_TO_NAVI\n");
                gchar buffer[80];
                gchar strlat[32];
                gchar strlon[32];
                g_ascii_formatd(strlat, 32, "%.06f", lat);
                g_ascii_formatd(strlon, 32, "%.06f", lon);
                snprintf(buffer, sizeof(buffer), "%s, %s", strlat, strlon);
                map_route_download_with_destination(buffer);
            	break;
            case GO_TO_DETAIL:
            	run_aprs_detail_dialog(parent,station->p_station);
            	fprintf(stderr,"GO_TO_DETAIL\n");

            	break;
            case GO_TO_MESSAGE:
            	message_edit_dialog(parent,station->p_station->call_sign);
            	fprintf(stderr,"GO_TO_MESSAGE\n");
            	break;
            }

            gtk_widget_destroy(dialog2);
        	g_free(title);


        	break;
    	}
    }




    //Free station List
    for(curr = station_list; curr; curr = curr->next)
    {
        SInfo *station_info = curr->data;
        if(station_info)
        {
            g_slice_free(SInfo, station_info);
        }
    }

    g_list_free(station_list);

}
void
map_menu_aprs_message_list()
{
    GtkWidget *dialog;
    MapController *controller;
    MapDialog *dlg;
    gint response,i;
    GtkWindow *parent;

    controller = map_controller_get_instance();
    dialog = map_dialog_new(_("ARPS Messages"),
                            map_controller_get_main_window(controller),
                            TRUE);
    dlg = (MapDialog *)dialog;


    for (i = 0; i < msg_index_end; i++)
    {
   		//(gdouble)msg_data[msg_index[i]].sec_heard
     	gchar * messagebtn = g_strdup_printf("%s>%s:%s",
       		 msg_data[msg_index[i]].from_call_sign,
       		 msg_data[msg_index[i]].call_sign,
       		 msg_data[msg_index[i]].message_line);
     	map_dialog_create_button_align(dlg, messagebtn, i);
    } // End of while loop



    response = gtk_dialog_run(GTK_DIALOG(dialog));
    gtk_widget_destroy(dialog);
    if(response>=0 && response<msg_index_end)
    {
    	parent = GTK_WINDOW(dialog);
    	//gchar callsign[20];
    	//snprintf(callsign, sizeof(callsign), "%s", );
    	message_edit_dialog(parent,msg_data[msg_index[response]].from_call_sign);
    }
    /*switch (response) {
    case GO_TO_LATLON:
        menu_cb_view_goto_latlon(NULL); break;
    case GO_TO_ADDRESS:
        menu_cb_view_goto_address(NULL); break;
    case GO_TO_GPS:
        menu_cb_view_goto_gps(NULL); break;
    case GO_TO_WAYPOINT:
        menu_cb_view_goto_nextway(NULL); break;
    case GO_TO_POI:
        menu_cb_view_goto_nearpoi(NULL); break;
    }*/
}
/* APRS DISPALY END */
void aprs_init()
{
	_aprs_thread_tnc_reading=TRUE;
	port_data.device_type = DEVICE_SERIAL_KISS_TNC;
	port_data.device_name[0]=0;
    _write_buffer[APRS_PORT_INET].write_lock = g_mutex_new();
    _write_buffer[APRS_PORT_TTY].write_lock = g_mutex_new();
    _aprs_thread_inet_reading=FALSE;
    _aprs_thread_tnc_reading=FALSE;

    auto_offset_init();
}

void aprs_destroy()
{
	aprs_tnc_destroy();
	aprs_inet_destroy();
}



void aprs_notification(gdouble lat,gdouble lon)
{

	static unsigned long int gps_packet_count=0;
	_gps.lat = lat;
	_gps.lon = lon;
	if(gps_packet_count % 300 == 0)
		auto_offset_set();
	gps_packet_count++;
}


